js命名规范

 

2 代码风格

2.1 文件

【建议】 JavaScript 文件使用无 BOM 的 UTF-8 编码。

解释:javascript

UTF-8 编码具备更普遍的适应性。BOM 在使用程序或工具处理文件时可能形成没必要要的干扰。html

【建议】 在文件结尾处,保留一个空行。

2.2 结构**【强制】**

2.2.1 缩进

【强制】 使用 4 个空格作为一个缩进层级,不容许使用 2 个空格 或 tab 字符。
【强制】 switch 下的 case 和 default 必须增长一个缩进层级。

示例:java

// good switch (variable) { case '1': // do... break; case '2': // do... break; default: // do... } // bad switch (variable) { case '1': // do... break; case '2': // do... break; default: // do... }

2.2.2 空格

【强制】 二元运算符两侧必须有一个空格,一元运算符与操做对象之间不容许有空格。

示例:node

var a = !arr.length; a++; a = b + c;
【强制】 用做代码块起始的左花括号 { 前必须有一个空格。

示例:git

// good if (condition) { } while (condition) { } function funcName() { } // bad if (condition){ } while (condition){ } function funcName(){ }
【强制】 if / else / for / while / function / switch / do / try / catch / finally 关键字后,必须有一个空格。

示例:github

// good if (condition) { } while (condition) { } (function () { })(); // bad if(condition) { } while(condition) { } (function() { })();
【强制】 在对象建立时,属性中的 : 以后必须有空格,: 以前不容许有空格。

示例:ajax

// good var obj = { a: 1, b: 2, c: 3 }; // bad var obj = { a : 1, b:2, c :3 };
【强制】 函数声明、具名函数表达式、函数调用中,函数名和 ( 之间不容许有空格。

示例:正则表达式

// good function funcName() { } var funcName = function funcName() { }; funcName(); // bad function funcName () { } var funcName = function funcName () { }; funcName ();
【强制】 , 和 ; 前不容许有空格。

示例:算法

// good callFunc(a, b); // bad callFunc(a , b) ;
【强制】 在函数调用、函数声明、括号表达式、属性访问、if / for / while / switch / catch 等语句中,() 和 [] 内紧贴括号部分不容许有空格。

示例:express

// good callFunc(param1, param2, param3); save(this.list[this.indexes[i]]); needIncream && (variable += increament); if (num > list.length) { } while (len--) { } // bad callFunc( param1, param2, param3 ); save( this.list[ this.indexes[ i ] ] ); needIncreament && ( variable += increament ); if ( num > list.length ) { } while ( len-- ) { }
【强制】 单行声明的数组与对象,若是包含元素,{} 和 [] 内紧贴括号部分不容许包含空格。

解释:

声明包含元素的数组与对象,只有当内部元素的形式较为简单时,才容许写在一行。元素复杂的状况,仍是应该换行书写。

示例:

// good var arr1 = []; var arr2 = [1, 2, 3]; var obj1 = {}; var obj2 = {name: 'obj'}; var obj3 = { name: 'obj', age: 20, sex: 1 }; // bad var arr1 = [ ]; var arr2 = [ 1, 2, 3 ]; var obj1 = { }; var obj2 = { name: 'obj' }; var obj3 = {name: 'obj', age: 20, sex: 1};
【强制】 行尾不得有多余的空格。

2.2.3 换行

【强制】 每一个独立语句结束后必须换行。
【强制】 每行不得超过 120 个字符。

解释:

超长的不可分割的代码容许例外,好比复杂的正则表达式。长字符串不在例外之列。

【强制】 运算符处换行时,运算符必须在新行的行首。

示例:

// good if (user.isAuthenticated() && user.isInRole('admin') && user.hasAuthority('add-admin') || user.hasAuthority('delete-admin') ) { // Code } var result = number1 + number2 + number3 + number4 + number5; // bad if (user.isAuthenticated() && user.isInRole('admin') && user.hasAuthority('add-admin') || user.hasAuthority('delete-admin')) { // Code } var result = number1 + number2 + number3 + number4 + number5;
【强制】 在函数声明、函数表达式、函数调用、对象建立、数组建立、for语句等场景中,不容许在 , 或 ; 前换行。

示例:

// good var obj = { a: 1, b: 2, c: 3 }; foo( aVeryVeryLongArgument, anotherVeryLongArgument, callback ); // bad var 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; }

2.2.4 语句

【强制】 不得省略语句结束的分号。
【强制】 在 if / else / for / do / while 语句中,即便只有一行,也不得省略块 {...}

示例:

// good if (condition) { callFunc(); } // bad if (condition) callFunc(); if (condition) callFunc();
【强制】 函数定义结束不容许添加分号。

示例:

// good function funcName() { } // bad function funcName() { }; // 若是是函数表达式,分号是不容许省略的。 var funcName = function () { };
【强制】 IIFE 必须在函数表达式外添加 (,非 IIFE 不得在函数表达式外添加 (

解释:

IIFE = Immediately-Invoked Function Expression.

额外的 ( 可以让代码在阅读的一开始就能判断函数是否当即被调用,进而明白接下来代码的用途。而不是一直拖到底部才恍然大悟。

示例:

// good var task = (function () { // Code return result; })(); var func = function () { }; // bad var task = function () { // Code return result; }(); var func = (function () { });

2.3 命名

【强制】 变量 使用 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 文档化注释

【强制】 为了便于代码阅读和自文档化,如下内容必须包含以 /**...*/ 形式的块注释中。

解释:

  1. 文件
  2. namespace
  3. 函数或方法
  4. 类属性
  5. 事件
  6. 全局变量
  7. 常量
【强制】 文档注释前必须空一行。
【建议】 自文档化的文档说明 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 细节注释

对于内部实现、不容易理解的逻辑说明、摘要信息等,咱们可能须要编写细节注释。

【建议】 细节注释遵循单行注释的格式。说明必须换行时,每行是一个单行注释的起始。

示例:

function foo(p1, p2) { // 这里对具体内部逻辑进行说明 // 说明太长须要换行 for (...) { .... } }
【强制】 有时咱们会使用一些特殊标记进行说明。特殊标记必须使用单行注释的形式。下面列举了一些经常使用标记:

解释:

  1. TODO: 有功能待实现。此时须要对将要实现的功能进行简单说明。
  2. FIXME: 该处代码运行没问题,但可能因为时间赶或者其余缘由,须要修正。此时须要对如何修正进行简单说明。
  3. HACK: 为修正某些问题而写的不太好或者使用了某些诡异手段的代码。此时须要对思路或诡异手段进行描述。
  4. XXX: 该处存在陷阱。此时须要对陷阱进行描述。

3 语言特性

3.1 变量

【强制】 变量在使用前必须经过 var 定义。

解释:

不经过 var 定义变量将致使变量污染全局环境。

示例:

// good var name = 'MyName'; // bad name = 'MyName';
【强制】 每一个 var 只能声明一个变量。

解释:

一个 var 声明多个变量,容易致使较长的行长度,而且在修改时容易形成逗号和分号的混淆。

示例:

// good var hangModules = []; var missModules = []; var visited = {}; // bad var hangModules = [], missModules = [], visited = {};
【强制】 变量必须 即用即声明,不得在函数或其它形式的代码块起始位置统一声明全部变量。

解释:

变量声明与使用的距离越远,出现的跨度越大,代码的阅读与维护成本越高。虽然JavaScript的变量是函数做用域,仍是应该根据编程中的意图,缩小变量出现的距离空间。

示例:

// good function 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; } // bad function 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

解释:

使用 === 能够避免等于判断中隐式的类型转换。

示例:

// good if (age === 30) { // ...... } // bad if (age == 30) { // ...... }
【建议】 尽量使用简洁的表达式。

示例:

// 字符串为空 // good if (!name) { // ...... } // bad if (name === '') { // ...... }
// 字符串非空 // good if (name) { // ...... } // bad if (name !== '') { // ...... }
// 数组非空 // good if (collection.length) { // ...... } // bad if (collection.length > 0) { // ...... }
// 布尔不成立 // good if (!notTrue) { // ...... } // bad if (notTrue === false) { // ...... }
// null 或 undefined // good if (noValue == null) { // ...... } // bad if (noValue === null || typeof noValue === 'undefined') { // ...... }
【建议】 按执行频率排列分支的顺序。

解释:

按执行频率排列分支的顺序好处是:

  1. 阅读的人容易找到最多见的状况,增长可读性。
  2. 提升执行效率。
【建议】 对于相同变量或表达式的多值条件,用 switch 代替 if

示例:

// good switch (typeof variable) { case 'object': // ...... break; case 'number': case 'boolean': case 'string': // ...... break; } // bad var type = typeof variable; if (type === 'object') { // ...... } else if (type === 'number' || type === 'boolean' || type === 'string') { // ...... }
【建议】 若是函数或全局中的 else 块后没有任何语句,能够删除 else

示例:

// good function getName() { if (name) { return name; } return 'unnamed'; } // bad function getName() { if (name) { return name; } else { return 'unnamed'; } }

3.3 循环

【建议】 不要在循环体中包含函数表达式,事先将函数提取到循环体外。

解释:

循环体中的函数表达式,运行过程当中会生成循环次数个函数对象。

示例:

// good function clicker() { // ...... } for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; addListener(element, 'click', clicker); } // bad for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; addListener(element, 'click', function () {}); }
【建议】 对循环内屡次使用的不变值,在循环外用变量缓存。

示例:

// good var width = wrap.offsetWidth + 'px'; for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; element.style.width = width; // ...... } // bad for (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。对象类型检测使用 instanceofnull 或 undefined 的检测使用 == null

示例:

// string typeof variable === 'string' // number typeof variable === 'number' // boolean typeof variable === 'boolean' // Function typeof variable === 'function' // Object typeof variable === 'object' // RegExp variable instanceof RegExp // Array variable instanceof Array // null variable === null // null or undefined variable == null // undefined typeof variable === 'undefined'

3.4.2 类型转换

【建议】 转换成 string 时,使用 + ''

示例:

// good num + ''; // bad new String(num); num.toString(); String(num);
【建议】 转换成 number 时,一般使用 +

示例:

// good +str; // bad Number(str);
【建议】 string 转换成 number,要转换的字符串结尾包含非数字并指望忽略时,使用 parseInt

示例:

var width = '200px'; parseInt(width, 10);
【强制】 使用 parseInt 时,必须指定进制。

示例:

// good parseInt(str, 10); // bad parseInt(str);
【建议】 转换成 boolean 时,使用 !!

示例:

var num = 3.14; !!num;
【建议】 number 去除小数点,使用 Math.floor / Math.round / Math.ceil,不使用 parseInt

示例:

// good var num = 3.14; Math.ceil(num); // bad var num = 3.14; parseInt(num, 10);

3.5 字符串

【强制】 字符串开头和结束使用单引号 '

解释:

  1. 输入单引号不须要按住 shift,方便输入。
  2. 实际使用中,字符串常常用来拼接 HTML。为方便 HTML 中包含双引号而不须要转义写法。

示例:

var str = '我是一个字符串'; var html = '<div class="cls">拼接HTML能够省去双引号转义</div>';
【建议】 使用 数组 或 + 拼接字符串。

解释:

  1. 使用 + 拼接字符串,若是拼接的所有是 StringLiteral,压缩工具能够对其进行自动合并的优化。因此,静态字符串建议使用 + 拼接。
  2. 在现代浏览器下,使用 + 拼接字符串,性能较数组的方式要高。
  3. 如须要兼顾老旧浏览器,应尽可能使用数组拼接字符串。

示例:

// 使用数组拼接字符串 var str = [ // 推荐换行开始并缩进开始第一个字符串, 对齐代码, 方便阅读. '<ul>', '<li>第一项</li>', '<li>第二项</li>', '</ul>' ].join(''); // 使用 + 拼接字符串 var str2 = '' // 建议第一个为空字符串, 第二个换行开始并缩进开始, 对齐代码, 方便阅读 + '<ul>', + '<li>第一项</li>', + '<li>第二项</li>', + '</ul>';
【建议】 复杂的数据到视图字符串的转换过程,选用一种模板引擎。

解释:

使用模板引擎有以下好处:

  1. 在开发过程当中专一于数据,将视图生成的过程由另一个层级维护,使程序逻辑结构更清晰。
  2. 优秀的模板引擎,经过模板编译技术和高质量的编译产物,能得到比手工拼接字符串更高的性能。
  • artTemplate: 体积较小,在全部环境下性能高,语法灵活。
  • dot.js: 体积小,在现代浏览器下性能高,语法灵活。
  • etpl: 体积较小,在全部环境下性能高,模板复用性高,语法灵活。
  • handlebars: 体积大,在全部环境下性能高,扩展性高。
  • hogon: 体积小,在现代浏览器下性能高。
  • nunjucks: 体积较大,性能通常,模板复用性高。

3.6 对象

【强制】 使用对象字面量 {} 建立新 Object

示例:

// good var obj = {}; // bad var obj = new Object();
【强制】 对象建立时,若是一个对象的全部 属性 都可以不添加引号,则全部 属性 不得添加引号。

示例:

var info = { name: 'someone', age: 28 };
【强制】 对象建立时,若是任何一个 属性 须要添加引号,则全部 属性 必须添加 '

解释:

若是属性不符合 Identifier 和 NumberLiteral 的形式,就须要以 StringLiteral 的形式提供。

示例:

// good var info = { 'name': 'someone', 'age': 28, 'more-info': '...' }; // bad var 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 数组

【强制】 使用数组字面量 [] 建立新数组,除非想要建立的是指定长度的数组。

示例:

// good var arr = []; // bad var 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 方法。如下两种场景能够本身实现排序:

  1. 须要稳定的排序算法,达到严格一致的排序结果。
  2. 数据特色鲜明,适合使用桶排。
【建议】 清空数组使用 .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 个之内,过多参数会致使维护难度增大。

【建议】 经过 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 时:

  1. 若是 fn 的 [[Scope]] 是ObjectEnvironment(with 表达式生成 ObjectEnvironment,函数和 catch 表达式生成 DeclarativeEnvironment),则:
    1. 若是是 V8 引擎,则退出全过程。
    2. 若是是 SpiderMonkey,则处理该 ObjectEnvironment 的外层 LexicalEnvironment。
  2. 获取当前 LexicalEnvironment 下的全部类型为 Function 的对象,对于每个 Function 对象,分析其 FunctionBody:
    1. 若是 FunctionBody 中含有 直接调用eval,则退出全过程。
    2. 不然获得全部的 Identifier。
    3. 对于每个 Identifier,设其为 name,根据查找变量引用的规则,从 LexicalEnvironment 中找出名称为 name 的绑定 binding。
    4. 对 binding 添加 notSwap 属性,其值为 true。
  3. 检查当前 LexicalEnvironment 中的每个变量绑定,若是该绑定有 notSwap 属性且值为 true,则:
    1. 若是是V8引擎,删除该绑定。
    2. 若是是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等于对象时,须要修正constructor Animal.prototype = { constructor: Animal, jump: function () { alert('animal ' + this.name + ' jump'); } }; // 这种方式扩展prototype则无需理会constructor Animal.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 参数。若是事件须要传递较多信息,应仔细设计事件对象。

解释:

一个事件对象的好处有:

  1. 顺序无关,避免事件监听者须要记忆参数顺序。
  2. 每一个事件信息均可以根据须要提供或者不提供,更自由。
  3. 扩展方便,将来添加事件信息时,无需考虑会破坏监听器参数形式而没法向后兼容。
【建议】 设计自定义事件时,应考虑禁止默认行为。

解释:

常见禁止默认行为的方式有两种:

  1. 事件监听函数中 return false。
  2. 事件对象中包含禁止默认行为的方法,如 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 的使用。

解释:

若是没有特别的需求,减小或避免使用deletedelete的使用会破坏部分 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 DOM

4.1.1 元素获取

【建议】 对于单个元素,尽量使用 document.getElementById 获取,避免使用document.all
【建议】 对于多个元素的集合,尽量使用 context.getElementsByTagName 获取。其中 context 能够为 document 或其余元素。指定 tagName 参数为 * 能够得到全部子元素。
【建议】 遍历元素集合时,尽可能缓存集合长度。如需屡次操做同一集合,则应将集合转为数组。

解释:

原生获取元素集合的结果并不直接引用 DOM 元素,而是对索引进行读取,因此 DOM 结构的改变会实时反映到结果中。

示例:

<div></div> <span></span> <script> var elements = document.getElementsByTagName('*');  // 显示为 DIV alert(elements[0].tagName);  var div = elements[0]; var p = document.createElement('p'); docpment.body.insertBefore(p, div);  // 显示为 P alert(elements[0].tagName); </script>
【建议】 获取元素的直接子元素时使用 children。避免使用childNodes,除非预期是须要包含文本、注释和属性类型的节点。

4.1.2 样式获取

【建议】 获取元素实际样式信息时,应使用 getComputedStyle 或 currentStyle

解释:

经过 style 只能得到内联定义或经过 JavaScript 直接设置的样式。经过 CSS class 设置的元素样式没法直接经过 style 获取。

4.1.3 样式设置

【建议】 尽量经过为元素添加预约义的 className 来改变元素样式,避免直接操做 style 设置。
【强制】 经过 style 对象设置元素样式时,对于带单位非 0 值的属性,不容许省略单位。

解释:

除了 IE,标准浏览器会忽略不规范的属性值,致使兼容性问题。

4.1.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 操做有助于提升性能。举一个简单的例子,构建一个列表。咱们能够用两种方式:

  1. 在循环体中 createElement 并 append 到父元素中。
  2. 在循环体中拼接 HTML 字符串,循环结束后写父元素的 innerHTML。

第一种方法看起来比较标准,可是每次循环都会对 DOM 进行操做,性能极低。在这里推荐使用第二种方法。

4.1.5 DOM 事件

【建议】 优先使用 addEventListener / attachEvent 绑定事件,避免直接在 HTML 属性中或 DOM 的 expando 属性绑定事件处理。

解释:

expando 属性绑定事件容易致使互相覆盖。

【建议】 使用 addEventListener 时第三个参数使用 false

解释:

标准浏览器中的 addEventListener 能够经过第三个参数指定两种时间触发模型:冒泡和捕获。而 IE 的 attachEvent 仅支持冒泡的事件触发。因此为了保持一致性,一般 addEventListener 的第三个参数都为 false。

相关文章
相关标签/搜索