JavaScript在百度一贯有着广泛的运用,特殊是在浏览器真个行为管理。本文档的目标是使JavaScript代码风格保持同等,随意马虎被理解和被掩护。
虽然本文档是针对JavaScript设计的,但是在利用各种JavaScript的预编译措辞时(如TypeScript等)时,适用的部分也应只管即便遵照本文档的约定。
2 代码风格

2.1 文件
[建议] JavaScript 文件利用无 BOM 的 UTF-8 编码。
阐明:UTF-8 编码具有更广泛的适应性。BOM 在利用程序或工具处理文件时可能造成不必要的滋扰。
[建议] 在文件结尾处,保留一个空行。
2.2 构造
[逼迫] 利用 4 个空格做为一个缩进层级,不许可利用 2 个空格 或 tab 字符。
[逼迫] switch 下的 case 和 default 必须增加一个缩进层级。
// goodswitch (variable) { case '1': // do... break; case '2': // do... break; default: // do...}// badswitch (variable) {case '1': // do... break;case '2': // do... break;default: // do...}
[逼迫] 二元运算符两侧必须有一个空格,一元运算符与操为难刁难象之间不许可有空格。
var a = !arr.length;a++;a = b + c;
[逼迫] 用作代码块起始的左花括号 { 前必须有一个空格。
示例:
// goodif (condition) {}while (condition) {}function funcName() {}// badif (condition){}while (condition){}function funcName(){}
[逼迫] if / else / for / while / function / switch / do / try / catch / finally 关键字后,必须有一个空格。
// goodif (condition) {}while (condition) {}(function () {})();// badif(condition) {}while(condition) {}(function() {})();
[逼迫] 在工具创建时,属性中的 : 之后必须有空格,: 之前不许可有空格。
// goodvar obj = { a: 1, b: 2, c: 3};// badvar obj = { a : 1, b:2, c :3};
[逼迫] 函数声明、具名函数表达式、函数调用中,函数名和 ( 之间不许可有空格。
// goodfunction funcName() {}var funcName = function funcName() {};funcName();// badfunction funcName () {}var funcName = function funcName () {};funcName ();
[逼迫] , 和 ; 前不许可有空格。
// goodcallFunc(a, b);// badcallFunc(a , b) ;
[逼迫] 在函数调用、函数声明、括号表达式、属性访问、if / for / while / switch / catch 等语句中,() 和 [] 内紧贴括号部分不许可有空格。
// goodcallFunc(param1, param2, param3);save(this.list[this.indexes[i]]);needIncream && (variable += increament);if (num > list.length) {}while (len--) {}// badcallFunc( param1, param2, param3 );save( this.list[ this.indexes[ i ] ] );needIncreament && ( variable += increament );if ( num > list.length ) {}while ( len-- ) {}
[逼迫] 单行声明的数组与工具,如果包含元素,{} 和 [] 内紧贴括号部分不许可包含空格。
阐明:声明包含元素的数组与工具,只有当内部元素的形式较为大略时,才许可写在一行。元素繁芜的情形,还是该当换行书写。
// goodvar arr1 = [];var arr2 = [1, 2, 3];var obj1 = {};var obj2 = {name: 'obj'};var obj3 = { name: 'obj', age: 20, sex: 1};// badvar arr1 = [ ];var arr2 = [ 1, 2, 3 ];var obj1 = { };var obj2 = { name: 'obj' };var obj3 = {name: 'obj', age: 20, sex: 1};
[逼迫] 行尾不得有多余的空格。
[逼迫] 每个独立语句结束后必须换行。
[逼迫] 每行不得超过 120 个字符。
阐明:超长的不可分割的代码许可例外,比如繁芜的正则表达式。长字符串不在例外之列。
[逼迫] 运算符处换行时,运算符必须在新行的行首。
// goodif (user.isAuthenticated() && user.isInRole('admin') && user.hasAuthority('add-admin') || user.hasAuthority('delete-admin')) { // Code}var result = number1 + number2 + number3 + number4 + number5;// badif (user.isAuthenticated() && user.isInRole('admin') && user.hasAuthority('add-admin') || user.hasAuthority('delete-admin')) { // Code}var result = number1 + number2 + number3 + number4 + number5;
[逼迫] 在函数声明、函数表达式、函数调用、工具创建、数组创建、for语句等场景中,不许可在 , 或 ; 前换行。
// goodvar obj = { a: 1, b: 2, c: 3};foo( aVeryVeryLongArgument, anotherVeryLongArgument, callback);// badvar obj = { a: 1 , b: 2 , c: 3};foo( aVeryVeryLongArgument , anotherVeryLongArgument , callback);
[建议] 不同行为或逻辑的语句集,利用空行隔开,更易阅读。
// 仅为按逻辑换行的示例,不代表setStyle的最优实现function setStyle(element, property, value) { if (element == null) { return; } element.style[property] = value;}
[建议] 在语句的行长度超过 120 时,根据逻辑条件合理缩进。
// 较繁芜的逻辑条件组合,将每个条件独立一行,逻辑运算符放置在行首进行分隔,或将部分逻辑按逻辑组合进行分隔。// 建议终极将右括号 ) 与左大括号 { 放在独立一行,担保与 if 内语句块能随意马虎视觉辨识。if (user.isAuthenticated() && user.isInRole('admin') && user.hasAuthority('add-admin') || user.hasAuthority('delete-admin')) { // Code}// 按一定长度截断字符串,并利用 + 运算符进行连接。// 分隔字符串只管即便按语义进行,如不要在一个完全的名词中间断开。// 特殊的,对付HTML片段的拼接,通过缩进,保持和HTML相同的构造。var html = '' // 此处用一个空字符串,以便全体HTML片段都在新行严格对齐 + '<article>' + '<h1>Title here</h1>' + '<p>This is a paragraph</p>' + '<footer>Complete</footer>' + '</article>';// 也可利用数组来进行拼接,相对 + 更随意马虎调度缩进。var html = [ '<article>', '<h1>Title here</h1>', '<p>This is a paragraph</p>', '<footer>Complete</footer>', '</article>'];html = html.join('');// 当参数过多时,将每个参数独立写在一行上,并将结束的右括号 ) 独立一行。// 所有参数必须增加一个缩进。foo( aVeryVeryLongArgument, anotherVeryLongArgument, callback);// 也可以按逻辑对参数进行组合。// 最经典的是baidu.format函数,调用时将参数分为“模板”和“数据”两块baidu.format( dateFormatTemplate, year, month, date, hour, minute, second);// 当函数调用时,如果有一个或以上参数超过多行,应该每一个参数独立一行。// 这常日涌如今匿名函数或者工具初始化等作为参数时,如setTimeout函数等。setTimeout( function () { alert('hello'); }, 200);order.data.read( 'id=' + me.model.id, function (data) { me.attchToModel(data.result); callback(); }, 300);// 链式调用较永劫采取缩进进行调度。$('#items') .find('.selected') .highlight() .end();// 三元运算符由3部分组成,因此其换行应该根据每个部分的长度不同,形身分歧的情形。var result = thisIsAVeryVeryLongCondition ? resultA : resultB;var result = condition ? thisIsAVeryVeryLongResult : resultB;// 数组和工具初始化的混用,严格按照每个工具的 { 和结束 } 在独立一行的风格书写。var array = [ { // ... }, { // ... }];
[建议] 对付 if...else...、try...catch...finally 等语句,推举利用在 } 号后添加一个换行 的风格,使代码层次构造更清晰,阅读性更好。
if (condition) { // some statements;}else { // some statements;}try { // some statements;}catch (ex) { // some statements;}
[逼迫] 不得省略语句结束的分号。
[逼迫] 在 if / else / for / do / while 语句中,纵然只有一行,也不得省略块 {...}。
// goodif (condition) { callFunc();}// badif (condition) callFunc();if (condition) callFunc();
[逼迫] 函数定义结束不许可添加分号。
// goodfunction funcName() {}// badfunction funcName() {};// 如果是函数表达式,分号是不许可省略的。var funcName = function () {};
[逼迫] IIFE 必须在函数表达式外添加 (,非 IIFE 不得在函数表达式外添加 (。
阐明:IIFE = Immediately-Invoked Function Expression.
额外的 ( 能够让代码在阅读的一开始就能判断函数是否立即被调用,进而明白接下来代码的用场。而不是一贯拖到底部才恍然大悟。
// goodvar task = (function () { // Code return result;})();var func = function () {};// badvar task = function () { // Code return result;}();var func = (function () {});
2.3 命名
下面提到的 Camel命名法:驼峰命名法;Pascal命名法:帕斯卡命名法,又叫大驼峰命名法。
[逼迫] 变量 利用 Camel命名法。
var loadingModules = {};
[逼迫] 常量 利用 全部字母大写,单词间下划线分隔 的命名办法。
var HTML_ENTITY = {};
[逼迫] 函数 利用 Camel命名法。
function stringFormat(source) {}
[逼迫] 函数的 参数 利用 Camel命名法。
function hear(theBells) {}
[逼迫] 类 利用 Pascal命名法。
function TextNode(options) {}
[逼迫] 类的 方法 / 属性 利用 Camel命名法。
function TextNode(value, engine) { this.value = value; this.engine = engine;}TextNode.prototype.clone = function () { return this;};
[逼迫] 列举变量 利用 Pascal命名法,列举的属性 利用 全部字母大写,单词间下划线分隔 的命名办法。
var TargetState = { READING: 1, READED: 2, APPLIED: 3, READY: 4};
[逼迫] 命名空间 利用 Camel命名法。
equipments.heavyWeapons = {};
[逼迫] 由多个单词组成的缩写词,在命名中,根据当前命名法和涌现的位置,所有字母的大小写与首字母的大小写保持同等。
function XMLParser() {}function insertHTML(element, html) {}var httpRequest = new HTTPRequest();
[逼迫] 类名 利用 名词。
function Engine(options) {}
[建议] 函数名 利用 动宾短语。
function getStyle(element) {}
[建议] boolean 类型的变量利用 is 或 has 开头。
var isReady = false;var hasMoreCommands = false;
[建议] Promise工具 用 动宾短语的进行时 表达。
var loadingData = ajax.get('url');loadingData.then(callback);
2.4 注释
2.4.1 单行注释
[逼迫] 必须独占一行。// 后跟一个空格,缩进与下一行被注释解释的代码同等。
2.4.2 多行注释
[建议] 避免利用 /.../ 这样的多行注释。有多行注释内容时,利用多个单行注释。
2.4.3 文档化注释
[逼迫] 为了便于代码阅读和自文档化,以下内容必须包含以 /.../ 形式的块注释中。
阐明:
文件namespace类函数或方法类属性事宜全局变量常量AMD 模块[逼迫] 文档注释前必须空一行。
[建议] 自文档化的文档解释 what,而不是 how。
2.4.4 类型定义
[逼迫] 类型定义都因此{开始, 以}结束。
阐明:常用类型如:{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}。
类型不仅局限于内置的类型,也可以是自定义的类型。比如定义了一个类 Developer,就可以利用它来定义一个参数和返回值的类型。
[逼迫] 对付基本类型 {string}, {number}, {boolean},首字母必须小写。
类型定义 语法示例 阐明 String {string} -- Number {number} -- Boolean {boolean} -- Object {Object} -- Function {Function} -- RegExp {RegExp} -- Array {Array} -- Date {Date} -- 单一类型凑集 {Array.<string>} string 类型的数组 多类型 {(number|boolean)} 可能是 number 类型, 也可能是 boolean 类型 许可为null {?number} 可能是 number, 也可能是 null 不许可为null {!Object} Object 类型, 但不是 null Function类型 {function(number, boolean)} 函数, 形参类型 Function带返回值 {function(number, boolean):string} 函数, 形参, 返回值类型 参数可选 @param {string=} name 可选参数, =为类型后缀 可变参数 @param {...number} args 变长参数, ...为类型前缀 任意类型 {} 任意类型 可选任意类型 @param {=} name 可选参数,类型不限 可变任意类型 @param {...} args 变长参数,类型不限 2.4.5 文件注释
[逼迫] 文件顶部必须包含文件注释,用 @file 标识文件解释。
/ @file Describe the file /
[建议] 文件注释中可以用 @author 标识开拓者信息。
阐明:
开拓者信息能够表示开拓职员对文件的贡献,并且能够让碰着问题或希望理解干系信息的人找到掩护人。常日情形文件在被创建时标识的是创建者。随着项目的进展,越来越多的人加入,参与这个文件的开拓,新的作者该当被加入 @author 标识。
@author 标识具有多人时,原则是按照 任务 进行排序。常日的说便是如果有问题,便是找第一个人该当比找第二个人有效。比如文件的创建者由于各种缘故原由,模块移交给了其他人或其他团队,后来由于新增需求,其他人在新增代码时,添加 @author 标识该当把自己的名字添加在创建人的前面。
@author 中的名字不许可被删除。任何劳动成果都该当被尊重。
业务项目中,一个文件可能被多人频繁修正,并且每个人的掩护韶光都可能不会很长,不建议为文件增加 @author 标识。通过版本掌握系统追踪变更,按业务逻辑单元确定模块的掩护任务人,通过文档与wiki跟踪和查询,是更好的任务管理办法。
对付业务逻辑无关的技能型根本项目,特殊是开源的公共项目,应利用 @author 标识。
/ @file Describe the file @author author-name(mail-name@domain.com) author-name2(mail-name2@domain.com) /
2.4.6 命名空间注释
[建议] 命名空间利用 @namespace 标识。
/ @namespace /var util = {};
2.4.7 类注释
[建议] 利用 @class 标记类或布局函数。
阐明:对付利用工具 constructor 属性来定义的布局函数,可以利用 @constructor 来标记。
/ 描述 @class /function Developer() { // constructor body}
[建议] 利用 @extends 标记类的继续信息。
/ 描述 @class @extends Developer /function Fronteer() { Developer.call(this); // constructor body}util.inherits(Fronteer, Developer);
[逼迫] 利用包装办法扩展类成员时, 必须通过 @lends 进行重新指向。
阐明:没有 @lends 标记将无法为该类天生包含扩展类成员的文档。
/ 类描述 @class @extends Developer /function Fronteer() { Developer.call(this); // constructor body}util.extend( Fronteer.prototype, / @lends Fronteer.prototype /{ _getLevel: function () { // TODO } });
[逼迫] 类的属性或方法等成员信息利用 @public / @protected / @private 中的任意一个,指明可访问性。
阐明:天生的文档中将有可访问性的标记,避免用户直策应用非 public 的属性或方法。
/ 类描述 @class @extends Developer /var Fronteer = function () { Developer.call(this); / 属性描述 @type {string} @private / this._level = 'T12'; // constructor body};util.inherits(Fronteer, Developer);/ 方法描述 @private @return {string} 返回值描述 /Fronteer.prototype._getLevel = function () {};
2.4.8 函数/方法注释
[逼迫] 函数/方法注释必须包含函数解释,有参数和返回值时必须利用注释标识。
[逼迫] 参数和返回值注释必须包含类型信息息争释。
[建议] 当函数是内部函数,外部不可访问时,可以利用 @inner 标识。
/ 函数描述 @param {string} p1 参数1的解释 @param {string} p2 参数2的解释,比较长 那就换行了. @param {number=} p3 参数3的解释(可选) @return {Object} 返回值描述 /function foo(p1, p2, p3) { var p3 = p3 || 10; return { p1: p1, p2: p2, p3: p3 };}
[逼迫] 对 Object 中各项的描述, 必须利用 @param 标识。
/ 函数描述 @param {Object} option 参数描述 @param {string} option.url option项描述 @param {string=} option.method option项描述,可选参数 /function foo(option) { // TODO}
[建议] 重写父类方法时, 应该添加 @override 标识。如果重写的形参个数、类型、顺序和返回值类型均未发生变革,可省略 @param、@return,仅用 @override 标识,否则仍应作完全注释。
阐明:简而言之,当子类重写的方法能直接套用父类的方法注释时可省略对参数与返回值的注释。
2.4.9 事宜注释
[逼迫] 必须利用 @event 标识事宜,事宜参数的标识与方法描述的参数标识相同。
/ 值变更时触发 @event @param {Object} e e描述 @param {string} e.before before描述 @param {string} e.after after描述 /onchange: function (e) {}
[逼迫] 在会广播事宜的函数前利用 @fires 标识广播的事宜,在广播事宜代码前利用 @event 标识事宜。
[建议] 对付事宜工具的注释,利用 @param 标识,天生文档时可读性更好。
/ 点击处理 @fires Select#change @private /Select.prototype.clickHandler = function () { / 值变更时触发 @event Select#change @param {Object} e e描述 @param {string} e.before before描述 @param {string} e.after after描述 / this.fire( 'change', { before: 'foo', after: 'bar' } );};
2.4.10 常量注释
[逼迫] 常量必须利用 @const 标记,并包含解释和类型信息。
/ 常量解释 @const @type {string} /var REQUEST_URL = 'myurl.do';
2.4.11 繁芜类型注释
[建议] 对付类型未定义的繁芜构造的注释,可以利用 @typedef 标识来定义。
// `namespaceA~` 可以换成其它 namepaths 前缀,目的是为了天生文档中能显示 `@typedef` 定义的类型和链接。/ 做事器 @typedef {Object} namespaceA~Server @property {string} host 主机 @property {number} port 端口 // 做事器列表 @type {Array.<namespaceA~Server>} /var servers = [ { host: '1.2.3.4', port: 8080 }, { host: '1.2.3.5', port: 8081 }];
2.4.12 AMD 模块注释
[逼迫] AMD 模块利用 @module 或 @exports 标识。
阐明:@exports 与 @module 都可以用来标识模块,差异在于 @module 可以省略模块名称。而只利用 @exports 时在 namepaths 中可以省略 module: 前缀。
define( function (require) { / foo description @exports Foo / var foo = { // TODO }; / baz description @return {boolean} return description / foo.baz = function () { // TODO }; return foo; });
也可以在 exports 变量前利用 @module 标识:
define( function (require) { / module description. @module foo / var exports = {}; / bar description / exports.bar = function () { // TODO }; return exports; });
如果直策应用 factory 的 exports 参数,还可以:
/ module description. @module /define( function (require, exports) { / bar description / exports.bar = function () { // TODO }; return exports; });
[逼迫] 对付已利用 @module 标识为 AMD模块 的引用,在 namepaths 中必须增加 module: 作前缀。
阐明:namepaths 没有 module: 前缀时,天生的文档中将无法精确天生链接。
/ 点击处理 @fires module:Select#change @private /Select.prototype.clickHandler = function () { / 值变更时触发 @event module:Select#change @param {Object} e e描述 @param {string} e.before before描述 @param {string} e.after after描述 / this.fire( 'change', { before: 'foo', after: 'bar' } );};
[建议] 对付类定义的模块,可以利用 @alias 标识构建函数。
/ A module representing a jacket. @module jacket /define( function () { / @class @alias module:jacket / var Jacket = function () { }; return Jacket; });
[建议] 多模块定义时,可以利用 @exports 标识各个模块。
// one moduledefine('html/utils', / Utility functions to ease working with DOM elements. @exports html/utils / function () { var exports = { }; return exports; });// another moduledefine('tag', / @exports tag / function () { var exports = { }; return exports; });
[建议] 对付 exports 为 Object 的模块,可以利用@namespace标识。
阐明:利用 @namespace 而不是 @module 或 @exports 时,对模块的引用可以省略 module: 前缀。
[建议] 对付 exports 为类名的模块,利用 @class 和 @exports 标识。
// 只利用 @class Bar 时,类方法和属性都必须增加 @name Bar#methodName 来标识,与 @exports 合营可以免除这一麻烦,并且在引用时可以省去 module: 前缀。// 其余须要把稳类名须要利用 var 定义的办法。/ Bar description @see foo @exports Bar @class /var Bar = function () { // TODO};/ baz description @return {(string|Array)} return description /Bar.prototype.baz = function () { // TODO};
2.4.13 细节注释
对付内部实现、不随意马虎理解的逻辑解释、择要信息等,我们可能须要编写细节注释。
[建议] 细节注释遵照单行注释的格式。解释必须换行时,每行是一个单行注释的起始。
function foo(p1, p2, opt_p3) { // 这里对详细内部逻辑进行解释 // 解释太长须要换行 for (...) { .... }}
[逼迫] 有时我们会利用一些分外标记进行解释。分外标记必须利用单行注释的形式。下面列举了一些常用标记:
阐明:
TODO: 有功能待实现。此时须要对将要实现的功能进行大略解释。FIXME: 该处代码运行没问题,但可能由于韶光赶或者其他缘故原由,须要改动。此时须要对如何改动进行大略解释。HACK: 为改动某些问题而写的不太好或者利用了某些诡异手段的代码。此时须要对思路或诡异手段进行描述。XXX: 该处存在陷阱。此时须要对陷阱进行描述。3 措辞特性
3.1 变量
[逼迫] 变量在利用前必须通过 var 定义。
阐明:不通过 var 定义变量将导致变量污染全局环境。
// goodvar name = 'MyName';// badname = 'MyName';
[逼迫] 每个 var 只能声明一个变量。
阐明:一个 var 声明多个变量,随意马虎导致较长的行长度,并且在修正时随意马虎造成逗号和分号的稠浊。
// goodvar hangModules = [];var missModules = [];var visited = {};// badvar hangModules = [], missModules = [], visited = {};
[逼迫] 变量必须 即用即声明,不得在函数或其它形式的代码块起始位置统一声明所有变量。
阐明: 变量声明与利用的间隔越远,涌现的跨度越大,代码的阅读与掩护本钱越高。虽然JavaScript的变量是函数浸染域,还是该当根据编程中的意图,缩小变量涌现的间隔空间。
// goodfunction kv2List(source) { var list = []; for (var key in source) { if (source.hasOwnProperty(key)) { var item = { k: key, v: source[key] }; list.push(item); } } return list;}// badfunction kv2List(source) { var list = []; var key; var item; for (key in source) { if (source.hasOwnProperty(key)) { item = { k: key, v: source[key] }; list.push(item); } } return list;}
3.2 条件
[逼迫] 在 Equality Expression 中利用类型严格的 ===。仅当判断 null 或 undefined 时,许可利用 == null。
阐明:利用 === 可以避免即是判断中隐式的类型转换。
// goodif (age === 30) { // ......}// badif (age == 30) { // ......}
[建议] 尽可能利用简洁的表达式。
// 字符串为空// goodif (!name) { // ......}// badif (name === '') { // ......}// 字符串非空// goodif (name) { // ......}// badif (name !== '') { // ......}// 数组非空// goodif (collection.length) { // ......}// badif (collection.length > 0) { // ......}// 布尔不成立// goodif (!notTrue) { // ......}// badif (notTrue === false) { // ......}// null 或 undefined// goodif (noValue == null) { // ......}// badif (noValue === null || typeof noValue === 'undefined') { // ......}
[建议] 按实行频率排列分支的顺序。
阐明:按实行频率排列分支的顺序好处是:
阅读的人随意马虎找到最常见的情形,增加可读性。提高实行效率。[建议] 对付相同变量或表达式的多值条件,用 switch 代替 if。
// goodswitch (typeof variable) { case 'object': // ...... break; case 'number': case 'boolean': case 'string': // ...... break;}// badvar type = typeof variable;if (type === 'object') { // ......} else if (type === 'number' || type === 'boolean' || type === 'string') { // ......}
[建议] 如果函数或全局中的 else 块后没有任何语句,可以删除 else。
示例:
// goodfunction getName() { if (name) { return name; } return 'unnamed';}// badfunction getName() { if (name) { return name; } else { return 'unnamed'; }}
3.3 循环
[建议] 不要在循环体中包含函数表达式,事先将函数提取到循环体外。
阐明:循环体中的函数表达式,运行过程中会天生循环次数个函数工具。
// goodfunction clicker() { // ......}for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; addListener(element, 'click', clicker);}// badfor (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; addListener(element, 'click', function () {});}
[建议] 对循环内多次利用的不变值,在循环外用变量缓存。
// goodvar width = wrap.offsetWidth + 'px';for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; element.style.width = width; // ......}// badfor (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; element.style.width = wrap.offsetWidth + 'px'; // ......}
[建议] 对有序凑集进行遍历时,缓存 length。
阐明:虽然当代浏览器都对数组长度进行了缓存,但对付一些宿主工具和老旧浏览器的数组工具,在每次 length 访问时会动态打算元素个数,此时缓存 length 能有效提高程序性能。
for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; // ......}
[建议] 对有序凑集进行顺序无关的遍历时,利用逆序遍历。
阐明:逆序遍历可以节省变量,代码比较优化。
var len = elements.length;while (len--) { var element = elements[len]; // ......}
3.4 类型
3.4.1 类型检测
[建议] 类型检测优先利用 typeof。工具类型检测利用 instanceof。null 或 undefined 的检测利用 == null。
// stringtypeof variable === 'string'// numbertypeof variable === 'number'// booleantypeof variable === 'boolean'// Functiontypeof variable === 'function'// Objecttypeof variable === 'object'// RegExpvariable instanceof RegExp// Arrayvariable instanceof Array// nullvariable === null// null or undefinedvariable == null// undefinedtypeof variable === 'undefined'
3.4.2 类型转换
[建议] 转换成 string 时,利用 + ''。
// goodnum + '';// badnew String(num);num.toString();String(num);
[建议] 转换成 number 时,常日利用 +。
// good+str;// badNumber(str);
[建议] string 转换成 number,要转换的字符串结尾包含非数字并期望忽略时,利用 parseInt。
var width = '200px';parseInt(width, 10);
[逼迫] 利用 parseInt 时,必须指定进制。
// goodparseInt(str, 10);// badparseInt(str);
[建议] 转换成 boolean 时,利用 !!。
var num = 3.14;!!num;
[建议] number 去除小数点,利用 Math.floor / Math.round / Math.ceil,不该用 parseInt。
// goodvar num = 3.14;Math.ceil(num);// badvar num = 3.14;parseInt(num, 10);
3.5 字符串
[逼迫] 字符串开头和结束利用单引号 '。
阐明:
输入单引号不须要按住 shift,方便输入。实际利用中,字符串常常用来拼接 HTML。为方便 HTML 中包含双引号而不须要转义写法。var str = '我是一个字符串';var html = '<div class=\公众cls\"大众>拼接HTML可以省去双引号转义</div>';
[建议] 利用 数组 或 + 拼接字符串。
阐明:
利用 + 拼接字符串,如果拼接的全部是 StringLiteral,压缩工具可以对其进行自动合并的优化。以是,静态字符串建议利用 + 拼接。在当代浏览器下,利用 + 拼接字符串,性能较数组的办法要高。如须要兼顾老旧浏览器,应只管即便利用数组拼接字符串。示例:
// 利用数组拼接字符串var str = [ // 推举换行开始并缩进开始第一个字符串, 对齐代码, 方便阅读. '<ul>', '<li>第一项</li>', '<li>第二项</li>', '</ul>'].join('');// 利用 + 拼接字符串var str2 = '' // 建议第一个为空字符串, 第二个换行开始并缩进开始, 对齐代码, 方便阅读 + '<ul>', + '<li>第一项</li>', + '<li>第二项</li>', + '</ul>';
[建议] 繁芜的数据到视图字符串的转换过程,选用一种模板引擎。
阐明:利用模板引擎有如下好处:
在开拓过程中专注于数据,将视图天生的过程由其余一个层级掩护,使程序逻辑构造更清晰。精良的模板引擎,通过模板编译技能和高质量的编译产物,能得到比手工拼接字符串更高的性能。artTemplate: 体积较小,在所有环境下性能高,语法灵巧。dot.js: 体积小,在当代浏览器下性能高,语法灵巧。etpl: 体积较小,在所有环境下性能高,模板复用性高,语法灵巧。handlebars: 体历年夜,在所有环境下性能高,扩展性高。hogon: 体积小,在当代浏览器下性能高。nunjucks: 体积较大,性能一样平常,模板复用性高。3.6 工具
[逼迫] 利用工具字面量 {} 创建新 Object。
// goodvar obj = {};// badvar obj = new Object();
[逼迫] 工具创建时,如果一个工具的所有 属性 均可以不添加引号,则所有 属性 不得添加引号。
var info = { name: 'someone', age: 28};
[逼迫] 工具创建时,如果任何一个 属性 须要添加引号,则所有 属性 必须添加 '。
阐明:如果属性不符合 Identifier 和 NumberLiteral 的形式,就须要以 StringLiteral 的形式供应。
// goodvar info = { 'name': 'someone', 'age': 28, 'more-info': '...'};// badvar info = { name: 'someone', age: 28, 'more-info': '...'};
[逼迫] 不许可修正和扩展任何原生工具和宿主工具的原型。
// 以下行为绝对禁止String.prototype.trim = function () {};
[建议] 属性访问时,只管即便利用 .。
阐明:属性名符合 Identifier 的哀求,就可以通过 . 来访问,否则就只能通过 [expr] 办法访问。
常日在 JavaScript 中声明的工具,属性命名是利用 Camel 命名法,用 . 来访问更清晰简洁。部分分外的属性(比如来自后真个JSON),可能采取不屈常的命名办法,可以通过 [expr] 办法访问。
info.age;info['more-info'];
[建议] for in 遍历工具时, 利用 hasOwnProperty 过滤掉原型中的属性。
var newInfo = {};for (var key in info) { if (info.hasOwnProperty(key)) { newInfo[key] = info[key]; }}
3.7 数组
[逼迫] 利用数组字面量 [] 创建新数组,除非想要创建的是指定长度的数组。
// goodvar arr = [];// badvar arr = new Array();
[逼迫] 遍历数组不该用 for in。
阐明:数组工具可能存在数字以外的属性, 这种情形下 for in 不会得到精确结果.
var arr = ['a', 'b', 'c'];arr.other = 'other things'; // 这里仅作演示, 实际中应利用Object类型// 精确的遍历办法for (var i = 0, len = arr.length; i < len; i++) { console.log(i);}// 缺点的遍历办法for (i in arr) { console.log(i);}
[建议] 不由于性能的缘故原由自己实现数组排序功能,只管即便利用数组的 sort 方法。
阐明:自己实现的常规排序算法,在性能上并不优于数组默认的 sort 方法。以下两种场景可以自己实现排序:
须要稳定的排序算法,达到严格同等的排序结果。数据特点光鲜,适宜利用桶排。[建议] 清空数组利用 .length = 0。
3.8 函数
3.8.1 函数长度
[建议] 一个函数的长度掌握在 50 行以内。
阐明:将过多的逻辑单元混在一个大函数中,易导致难以掩护。一个清晰易懂的函数该当完成单一的逻辑单元。繁芜的操作应进一步抽取,通过函数的调用来表示流程。
特定算法等不可分割的逻辑许可例外。
function syncViewStateOnUserAction() { if (x.checked) { y.checked = true; z.value = ''; } else { y.checked = false; } if (!a.value) { warning.innerText = 'Please enter it'; submitButton.disabled = true; } else { warning.innerText = ''; submitButton.disabled = false; }}// 直接阅读该函数会难以明确其主线逻辑,因此下方是一种更合理的表达办法:function syncViewStateOnUserAction() { syncXStateToView(); checkAAvailability();}function syncXStateToView() { if (x.checked) { y.checked = true; z.value = ''; } else { y.checked = false; }}function checkAAvailability() { if (!a.value) { displayWarningForAMissing(); } else { clearWarnignForA(); }}
3.8.2 参数设计
[建议] 一个函数的参数掌握在 6 个以内。
阐明:
撤除不定长参数以外,函数具备不同逻辑意义的参数建议掌握在 6 个以内,过多参数会导致掩护难度增大。
某些情形下,如利用 AMD Loader 的 require 加载多个模块时,其 callback 可能会存在较多参数,因此对函数参数的个数不做逼迫限定。
[建议] 通过 options 参数通报非数据输入型参数。
阐明:有些函数的参数并不是作为算法的输入,而是对算法的某些分支条件判断之用,此类参数建议通过一个 options 参数通报。
如下函数:
/ 移除某个元素 @param {Node} element 须要移除的元素 @param {boolean} removeEventListeners 是否同时将所有注册在元素上的事宜移除 /function removeElement(element, removeEventListeners) { element.parent.removeChild(element); if (removeEventListeners) { element.clearEventListeners(); }}
可以转换为下面的署名:
/ 移除某个元素 @param {Node} element 须要移除的元素 @param {Object} options 干系的逻辑配置 @param {boolean} options.removeEventListeners 是否同时将所有注册在元素上的事宜移除 /function removeElement(element, options) { element.parent.removeChild(element); if (options.removeEventListeners) { element.clearEventListeners(); }}
这种模式有几个显著的上风:
boolean 型的配置项具备名称,从调用的代码上更易理解其表达的逻辑意义。当配置项有增长时,无需无休止地增加参数个数,不会涌现 removeElement(element, true, false, false, 3) 这样难以理解的调用代码。当部分配置参数可选时,多个参数的形式非常难处理重载逻辑,而利用一个 options 工具只需判断属性是否存在,实现得以简化。3.8.3 闭包
[建议] 在适当的时候将闭包内大工具置为 null。
阐明:
在 JavaScript 中,无需特殊的关键词就可以利用闭包,一个函数可以任意访问在其定义的浸染域外的变量。须要把稳的是,函数的浸染域是静态的,即在定义时决定,与调用的机遇和办法没有任何关系。
闭包会阻挡一些变量的垃圾回收,对付较老旧的JavaScript引擎,可能导致外部所有变量均无法回收。
首先一个较为明确的结论是,以下内容会影响到闭包内变量的回收:
嵌套的函数中是否有利用该变量。嵌套的函数中是否有 直接调用eval。是否利用了 with 表达式。Chakra、V8 和 SpiderMonkey 将受以上成分的影响,表现出不尽相同又较为相似的回收策略,而JScript.dll和Carakan则完备没有这方面的优化,会完全保留全体 LexicalEnvironment 中的所有变量绑定,造成一定的内存花费。
由于对闭包内变量有回收优化策略的 Chakra、V8 和 SpiderMonkey 引擎的行为较为相似,因此可以总结如下,当返回一个函数 fn 时:
如果 fn 的 [[Scope]] 是ObjectEnvironment(with 表达式天生 ObjectEnvironment,函数和 catch 表达式天生 DeclarativeEnvironment),则:如果是 V8 引擎,则退出全过程。如果是 SpiderMonkey,则处理该 ObjectEnvironment 的外层 LexicalEnvironment。获取当前 LexicalEnvironment 下的所有类型为 Function 的工具,对付每一个 Function 工具,剖析其 FunctionBody:如果 FunctionBody 中含有 直接调用eval,则退出全过程。否则得到所有的 Identifier。对付每一个 Identifier,设其为 name,根据查找变量引用的规则,从 LexicalEnvironment 中找出名称为 name 的绑定 binding。对 binding 添加 notSwap 属性,其值为 true。检讨当前 LexicalEnvironment 中的每一个变量绑定,如果该绑定有 notSwap 属性且值为 true,则:如果是V8引擎,删除该绑定。如果是SpiderMonkey,将该绑定的值设为 undefined,将删除 notSwap 属性。对付Chakra引擎,暂无法得知是按 V8 的模式还是按 SpiderMonkey 的模式进行。
如果有 非常弘大 的工具,且估量会在 老旧的引擎 中实行,则利用闭包时,把稳将闭包不须要的工具置为空引用。
[建议] 利用 IIFE 避免 Lift 效应。
阐明:在引用函数外部变量时,函数实行时外部变量的值由运行时决定而非定义时,最范例的场景如下:
var tasks = [];for (var i = 0; i < 5; i++) { tasks[tasks.length] = function () { console.log('Current cursor is at ' + i); };}var len = tasks.length;while (len--) { tasks[len]();}
以上代码对 tasks 中的函数的实行均会输出 Current cursor is at 5,每每不符合预期。
此征象称为 Lift 效应 。办理的办法是通过额外加上一层闭包函数,将须要的外部变量作为参数通报来解除变量的绑定关系:
var tasks = [];for (var i = 0; i < 5; i++) { // 把稳有一层额外的闭包 tasks[tasks.length] = (function (i) { return function () { console.log('Current cursor is at ' + i); }; })(i);}var len = tasks.length;while (len--) { tasks[len]();}
3.8.4 空函数
[建议] 空函数不该用 new Function() 的形式。
var emptyFunction = function () {};
[建议] 对付性能有高哀求的场合,建议存在一个空函数的常量,供多处利用共享。
var EMPTY_FUNCTION = function () {};function MyClass() {}MyClass.prototype.abstractMethod = EMPTY_FUNCTION;MyClass.prototype.hooks.before = EMPTY_FUNCTION;MyClass.prototype.hooks.after = EMPTY_FUNCTION;
3.9 面向工具
[逼迫] 类的继续方案,实现时须要改动 constructor。
阐明:常日利用其他 library 的类继续方案都会进行 constructor 改动。如果是自己实现的类继续方案,须要进行 constructor 改动。
/ 构建类之间的继续关系 @param {Function} subClass 子类函数 @param {Function} superClass 父类函数 /function inherits(subClass, superClass) { var F = new Function(); F.prototype = superClass.prototype; subClass.prototype = new F(); subClass.prototype.constructor = subClass;}
[建议] 声明类时,担保 constructor 的精确性。
function Animal(name) { this.name = name;}// 直接prototype即是工具时,须要改动constructorAnimal.prototype = { constructor: Animal, jump: function () { alert('animal ' + this.name + ' jump'); }};// 这种办法扩展prototype则无需理会constructorAnimal.prototype.jump = function () { alert('animal ' + this.name + ' jump');};
[建议] 属性在布局函数中声明,方法在原型中声明。
阐明: 原型工具的成员被所有实例共享,能节约内存占用。以是编码时我们该当遵守这样的原则:原型工具包含程序不会修正的成员,如方法函数或配置项。
function TextNode(value, engine) { this.value = value; this.engine = engine;}TextNode.prototype.clone = function () { return this;};
[逼迫] 自定义事宜的 事宜名 必须全小写。
阐明:在 JavaScript 广泛运用的浏览器环境,绝大多数 DOM 事宜名称都是全小写的。为了遵照大多数 JavaScript 开拓者的习气,在设计自定义事宜时,事宜名也该当全小写。
[逼迫] 自定义事宜只能有一个 event 参数。如果事宜须要通报较多信息,应仔细设计事宜工具。
阐明:一个事宜工具的好处有:
顺序无关,避免事宜监听者须要影象参数顺序。每个事宜信息都可以根据须要供应或者不供应,更自由。扩展方便,未来添加事宜信息时,无需考虑会毁坏监听器参数形式而无法向后兼容。[建议] 设计自定义事宜时,应考虑禁止默认行为。
阐明:常见禁止默认行为的办法有两种:
事宜监听函数中 return false。事宜工具中包含禁止默认行为的方法,如 preventDefault。3.10 动态特性
3.10.1 eval
[逼迫] 避免利用直接 eval 函数。
阐明:直接 eval,指的因此函数办法调用 eval 的调用方法。直接 eval 调用实行代码的浸染域为本地浸染域,应该避免。
如果有分外情形须要利用直接 eval,需在代码中用详细的注释解释为何必须利用直接 eval,不能利用其它动态实行代码的办法,同时须要其他资深工程师进行 Code Review。
[建议] 只管即便避免利用 eval 函数。
3.10.2 动态实行代码
[建议] 利用 new Function 实行动态代码。
阐明:通过 new Function 天生的函数浸染域是全局利用域,不会影响当当前确当地浸染域。如果有动态代码实行的需求,建议利用 new Function。
var handler = new Function('x', 'y', 'return x + y;');var result = handler($('#x').val(), $('#y').val());
3.10.3 with
[建议] 只管即便不要利用 with。
阐明:利用 with 可能会增加代码的繁芜度,不利于阅读和管理;也会对性能有影响。大多数利用 with 的场景都能利用其他办法较好的替代。以是,只管即便不要利用 with。
3.10.4 delete
[建议] 减少 delete 的利用。
阐明:如果没有特殊的需求,减少或避免利用delete。delete的利用会毁坏部分 JavaScript 引擎的性能优化。
[建议] 处理 delete 可能产生的非常。
阐明:
对付有被遍历需求,且值 null 被认为具有业务逻辑意义的值的工具,移除某个属性必须利用 delete 操作。
在严格模式或IE下利用 delete 时,不能被删除的属性会抛出非常,因此在不愿定属性是否可以删除的情形下,建议添加 try-catch 块。
try { delete o.x;}catch (deleteError) { o.x = null;}
3.10.5 工具属性
[建议] 避免修正外部传入的工具。
阐明:
JavaScript 因其脚本措辞的动态特性,当一个工具未被 seal 或 freeze 时,可以任意添加、删除、修正属性值。
但是随意地对 非自身掌握的工具 进行修正,很随意马虎造成代码在不可预知的情形下涌现问题。因此,设计良好的组件、函数该当避免对外部传入的工具的修正。
下面代码的 selectNode 方法修正了由外部传入的 datasource 工具。如果 datasource 用在其它场合(如另一个 Tree 实例)下,会造成状态的混乱。
function Tree(datasource) { this.datasource = datasource;}Tree.prototype.selectNode = function (id) { // 从datasource中找出节点工具 var node = this.findNode(id); if (node) { node.selected = true; this.flushView(); }};
对付此类场景,须要利用额外的工具来掩护,利用由自身掌握,不与外部产生任何交互的 selectedNodeIndex 工具来掩护节点的选中状态,不对 datasource 作任何修正。
function Tree(datasource) { this.datasource = datasource; this.selectedNodeIndex = {};}Tree.prototype.selectNode = function (id) { // 从datasource中找出节点工具 var node = this.findNode(id); if (node) { this.selectedNodeIndex[id] = true; this.flushView(); }};
除此之外,也可以通过 deepClone 等手段将自身掩护的工具与外部传入的分离,担保不会相互影响。
[建议] 具备强类型的设计。
阐明:
如果一个属性被设计为 boolean 类型,则不要利用 1 / 0 作为其值。对付标识性的属性,如对代码体积有严格哀求,可以从一开始就设计为 number 类型且将 0 作为否定值。从 DOM 中取出的值常日为 string 类型,如果有工具或函数的吸收类型为 number 类型,提前作好转换,而不是期望工具、函数可以处理多类型的值。4 浏览器环境
4.1 模块化
4.1.1 AMD
[逼迫] 利用 AMD 作为模块定义。
阐明:
AMD 作为由社区认可的模块定义形式,供应多种重载供应灵巧的利用办法,并且绝大多数精良的 Library 都支持 AMD,适宜作为规范。
目前,比较成熟的 AMD Loader 有:
官方实现的 requirejs百度自己实现的 esl[逼迫] 模块 id 必须符合标准。
阐明:模块 id 必须符合以下约束条件:
类型为 string,并且是由 / 分割的一系列 terms 来组成。例如:this/is/a/module。term 该当符合 [a-zA-Z0-9_-]+ 规则。不应该有 .js 后缀。跟文件的路径保持同等。4.1.2 define
[建议] 定义模块时不要指明 id 和 dependencies。
阐明:
在 AMD 的设计思想里,模块名称是和所在路径干系的,匿名的模块更利于封包和迁移。模块依赖应在模块定义内部通过 local require 引用。
以是,推举利用 define(factory) 的形式进行模块定义。
define( function (require) { });
[建议] 利用 return 来返回模块定义。
阐明:利用 return 可以减少 factory 吸收的参数(不须要吸收 exports 和 module),在没有 AMD Loader 的场景下也更随意马虎进行大略的处理来假造一个 Loader。
define( function (require) { var exports = {}; // ... return exports; });
4.1.3 require
[逼迫] 全局运行环境中,require 必须以 async require 形式调用。
阐明:模块的加载过程是异步的,同步调用并无法担保得到精确的结果。
// goodrequire(['foo'], function (foo) {});// badvar foo = require('foo');
[逼迫] 模块定义中只许可利用 local require,不许可利用 global require。
阐明:
在模块定义中利用 global require,对封装性是一种毁坏。在 AMD 里,global require 是可以被重命名的。并且 Loader 乃至没有全局的 require 变量,而是用 Loader 名称做为 global require。模块定义不应该依赖利用的 Loader。[逼迫] Package在实现时,内部模块的 require 必须利用 relative id。
阐明:对付任何可能通过 发布-引入 的形式复用的第三方库、框架、包,开拓者所定义的名称不代表利用者利用的名称。因此不要基于任何名称的假设。在实现源码中,require 自身的其它模块时利用 relative id。
define( function (require) { var util = require('./util'); });
[建议] 不会被调用的依赖模块,在 factory 开始处统一 require。
阐明:有些模块是依赖的模块,但不会在模块实现中被直接调用,最为范例的是 css / js / tpl 等 Plugin 所引入的外部内容。此类内容建议放在模块定义最开始处统一引用。
define( function (require) { require('css!foo.css'); require('tpl!bar.tpl.html'); // ... });
4.2 DOM
4.2.1 元素获取
[建议] 对付单个元素,尽可能利用 document.getElementById 获取,避免利用document.all。
[建议] 对付多个元素的凑集,尽可能利用 context.getElementsByTagName 获取。个中 context 可以为 document 或其他元素。指定 tagName 参数为 可以得到所有子元素。
[建议] 遍历元素凑集时,只管即便缓存凑集长度。如需多次操作同一凑集,则应将凑集转为数组。
阐明:原生获取元素凑集的结果并不直接引用 DOM 元素,而是对索引进行读取,以是 DOM 构造的改变会实时反响到结果中。
<div></div><span></span><script>var elements = document.getElementsByTagName('');// 显示为 DIValert(elements[0].tagName);var div = elements[0];var p = document.createElement('p');document.body.insertBefore(p, div);// 显示为 Palert(elements[0].tagName);</script>
[建议] 获取元素的直接子元素时利用 children。避免利用childNodes,除非预期是须要包含文本、注释和属性类型的节点。
4.2.2 样式获取
[建议] 获取元素实际样式信息时,应利用 getComputedStyle 或 currentStyle。
阐明:通过 style 只能得到内联定义或通过 JavaScript 直接设置的样式。通过 CSS class 设置的元素样式无法直接通过 style 获取。
4.2.3 样式设置
[建议] 尽可能通过为元素添加预定义的 className 来改变元素样式,避免直接操作 style 设置。
[逼迫] 通过 style 工具设置元素样式时,对付带单位非 0 值的属性,不许可省略单位。
阐明:除了 IE,标准浏览器会忽略不规范的属性值,导致兼容性问题。
4.2.4 DOM 操作
[建议] 操作 DOM 时,只管即便减少页面 reflow。
阐明:页面 reflow 是非常耗时的行为,非常随意马虎导致性能瓶颈。下面一些场景会触发浏览器的reflow:
DOM元素的添加、修正(内容)、删除。运用新的样式或者修正任何影响元素布局的属性。Resize浏览器窗口、滚动页面。读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)) 。[建议] 只管即便减少 DOM 操作。
阐明:DOM 操作也是非常耗时的一种操作,减少 DOM 操作有助于提高性能。举一个大略的例子,构建一个列表。我们可以用两种办法:
在循环体中 createElement 并 append 到父元素中。在循环体中拼接 HTML 字符串,循环结束后写父元素的 innerHTML。第一种方法看起来比较标准,但是每次循环都会对 DOM 进行操作,性能极低。在这里推举利用第二种方法。
4.2.5 DOM 事宜
[建议] 优先利用 addEventListener / attachEvent 绑定事宜,避免直接在 HTML 属性中或 DOM 的 expando 属性绑定事宜处理。
阐明:expando 属性绑定事宜随意马虎导致相互覆盖。
[建议] 利用 addEventListener 时第三个参数利用 false。
阐明:标准浏览器中的 addEventListener 可以通过第三个参数指定两种韶光触发模型:冒泡和捕获。而 IE 的 attachEvent 仅支持冒泡的事宜触发。所以为了保持同等性,常日 addEventListener 的第三个参数都为 false。
[建议] 在没有事宜自动管理的框架支持下,应持有监听器函数的引用,在适当时候(元素开释、页面卸载等)移除添加的监听器。
作者:前端切图小弟,个人运营的"大众号:前端读者(fe_duzhe)