JavaScript
文件使用无 BOM
的 UTF-8
编码。解释:javascript
UTF-8 编码具备更普遍的适应性。BOM 在使用程序或工具处理文件时可能形成没必要要的干扰。html
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... }
示例: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};
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;
,
或 ;
前换行。示例:
// 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; }
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 () { });
变量
使用 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);
//
后跟一个空格,缩进与下一行被注释说明的代码一致。/*...*/
这样的多行注释。有多行注释内容时,使用多个单行注释。/**...*/
形式的块注释中。解释:
{
开始, 以}
结束。解释:
经常使用类型如:{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}。
类型不只局限于内置的类型,也能够是自定义的类型。好比定义了一个类 Developer,就可使用它来定义一个参数和返回值的类型。
类型定义 | 语法示例 | 解释 |
---|---|---|
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 | 变长参数,类型不限 |
@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) */
@namespace
标识。示例:
/** * @namespace */ var util = {};
@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 () { };
@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 }; }
@param
标识。示例:
/** * 函数描述 * * @param {Object} option 参数描述 * @param {string} option.url option项描述 * @param {string=} option.method option项描述,可选参数 */ function foo(option) { // TODO }
@override
标识。若是重写的形参个数、类型、顺序和返回值类型均未发生变化,可省略 @param
、@return
,仅用 @override
标识,不然仍应做完整注释。解释:
简而言之,当子类重写的方法能直接套用父类的方法注释时可省略对参数与返回值的注释。
@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' } ); };
@const
标记,并包含说明和类型信息。示例:
/** * 常量说明 * * @const * @type {string} */ var REQUEST_URL = 'myurl.do';
@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 } ];
对于内部实现、不容易理解的逻辑说明、摘要信息等,咱们可能须要编写细节注释。
示例:
function foo(p1, p2) { // 这里对具体内部逻辑进行说明 // 说明太长须要换行 for (...) { .... } }
解释:
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; }
===
。仅当判断 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') { // ...... }
解释:
按执行频率排列分支的顺序好处是:
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'; } }
解释:
循环体中的函数表达式,运行过程当中会生成循环次数个函数对象。
示例:
// 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]; // ...... }
typeof
。对象类型检测使用 instanceof
。null
或 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'
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);
'
。解释:
示例:
var str = '我是一个字符串'; var html = '<div class="cls">拼接HTML能够省去双引号转义</div>';
数组
或 +
拼接字符串。解释:
示例:
// 使用数组拼接字符串 var str = [ // 推荐换行开始并缩进开始第一个字符串, 对齐代码, 方便阅读. '<ul>', '<li>第一项</li>', '<li>第二项</li>', '</ul>' ].join(''); // 使用 + 拼接字符串 var str2 = '' // 建议第一个为空字符串, 第二个换行开始并缩进开始, 对齐代码, 方便阅读 + '<ul>', + '<li>第一项</li>', + '<li>第二项</li>', + '</ul>';
解释:
使用模板引擎有以下好处:
{}
建立新 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]; } }
[]
建立新数组,除非想要建立的是指定长度的数组。示例:
// 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 方法。如下两种场景能够本身实现排序:
.length = 0
。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(); } }
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(); } }
这种模式有几个显著的优点:
null
。解释:
在 JavaScript 中,无需特别的关键词就可使用闭包,一个函数能够任意访问在其定义的做用域外的变量。须要注意的是,函数的做用域是静态的,即在定义时决定,与调用的时机和方式没有任何关系。
闭包会阻止一些变量的垃圾回收,对于较老旧的JavaScript引擎,可能致使外部全部变量均没法回收。
首先一个较为明确的结论是,如下内容会影响到闭包内变量的回收:
Chakra、V8 和 SpiderMonkey 将受以上因素的影响,表现出不尽相同又较为类似的回收策略,而JScript.dll和Carakan则彻底没有这方面的优化,会完整保留整个 LexicalEnvironment 中的全部变量绑定,形成必定的内存消耗。
因为对闭包内变量有回收优化策略的 Chakra、V8 和 SpiderMonkey 引擎的行为较为类似,所以能够总结以下,当返回一个函数 fn 时:
对于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](); }
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;
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
参数。若是事件须要传递较多信息,应仔细设计事件对象。解释:
一个事件对象的好处有:
解释:
常见禁止默认行为的方式有两种:
eval
函数。解释:
直接 eval,指的是以函数方式调用 eval 的调用方法。直接 eval 调用执行代码的做用域为本地做用域,应当避免。
若是有特殊状况须要使用直接 eval,需在代码中用详细的注释说明为什么必须使用直接 eval,不能使用其它动态执行代码的方式,同时须要其余资深工程师进行 Code Review。
eval
函数。new Function
执行动态代码。解释:
经过 new Function 生成的函数做用域是全局使用域,不会影响当当前的本地做用域。若是有动态代码执行的需求,建议使用 new Function。
示例:
var handler = new Function('x', 'y', 'return x + y;'); var result = handler($('#x').val(), $('#y').val());
with
。解释:
使用 with 可能会增长代码的复杂度,不利于阅读和管理;也会对性能有影响。大多数使用 with 的场景都能使用其余方式较好的替代。因此,尽可能不要使用 with。
delete
的使用。解释:
若是没有特别的需求,减小或避免使用delete
。delete
的使用会破坏部分 JavaScript 引擎的性能优化。
delete
可能产生的异常。解释:
对于有被遍历需求,且值 null 被认为具备业务逻辑意义的值的对象,移除某个属性必须使用 delete 操做。
在严格模式或IE下使用 delete 时,不能被删除的属性会抛出异常,所以在不肯定属性是否能够删除的状况下,建议添加 try-catch 块。
示例:
try {
delete o.x; } catch (deleteError) { o.x = null; }
解释:
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 等手段将自身维护的对象与外部传入的分离,保证不会相互影响。
解释:
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
,除非预期是须要包含文本、注释和属性类型的节点。getComputedStyle
或 currentStyle
。解释:
经过 style 只能得到内联定义或经过 JavaScript 直接设置的样式。经过 CSS class 设置的元素样式没法直接经过 style 获取。
解释:
除了 IE,标准浏览器会忽略不规范的属性值,致使兼容性问题。
DOM
时,尽可能减小页面 reflow
。解释:
页面 reflow 是很是耗时的行为,很是容易致使性能瓶颈。下面一些场景会触发浏览器的reflow:
DOM
操做。解释:
DOM 操做也是很是耗时的一种操做,减小 DOM 操做有助于提升性能。举一个简单的例子,构建一个列表。咱们能够用两种方式:
第一种方法看起来比较标准,可是每次循环都会对 DOM 进行操做,性能极低。在这里推荐使用第二种方法。
addEventListener / attachEvent
绑定事件,避免直接在 HTML 属性中或 DOM 的 expando
属性绑定事件处理。解释:
expando 属性绑定事件容易致使互相覆盖。
addEventListener
时第三个参数使用 false
。解释:
标准浏览器中的 addEventListener 能够经过第三个参数指定两种时间触发模型:冒泡和捕获。而 IE 的 attachEvent 仅支持冒泡的事件触发。因此为了保持一致性,一般 addEventListener 的第三个参数都为 false。