转载:原地点
1 媒介
JavaScript在百度一向有着普遍的运用,迥殊是在浏览器端的行动治理。本文档的目标是使JavaScript代码作风坚持一致,轻易被邃晓和被保护。
虽然本文档是针对JavaScript设想的,然则在运用种种JavaScript的预编译言语时(如TypeScript等)时,实用的部份也应只管遵照本文档的商定。
2 代码作风
2.1 文件
[发起] JavaScript
文件运用无 BOM
的 UTF-8
编码。
诠释:
UTF-8 编码具有更普遍的适应性。BOM 在运用递次或东西处置惩罚文件时可以构成不必要的滋扰。
[发起] 在文件末端处,保存一个空行。
2.2 组织
2.2.1 缩进
[强迫] 运用 4
个空格做为一个缩进层级,不许可运用 2
个空格 或 tab
字符。
[强迫] switch
下的 case
和 default
必需增添一个缩进层级。
示例:
javascript
// 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 空格
[强迫] 二元运算符两侧必需有一个空格,一元运算符与操纵对象之间不许可有空格。
示例:
javascript
var a = !arr.length; a++; a = b + c;
[强迫] 用作代码块肇端的左花括号 {
前必需有一个空格。
示例:
javascript
// good if (condition) { } while (condition) { } function funcName() { } // bad if (condition){ } while (condition){ } function funcName(){ }
[强迫] if / else / for / while / function / switch / do / try / catch / finally
症结字后,必需有一个空格。
示例:
javascript
// good if (condition) { } while (condition) { } (function () { })(); // bad if(condition) { } while(condition) { } (function() { })();
[强迫] 在对象建立时,属性中的 :
以后必需有空格,:
之前不许可有空格。
示例:
javascript
// good var obj = { a: 1, b: 2, c: 3 }; // bad var obj = { a : 1, b:2, c :3 };
[强迫] 函数声明、签字函数表达式、函数挪用中,函数名和 (
之间不许可有空格。
示例:
javascript
// good function funcName() { } var funcName = function funcName() { }; funcName(); // bad function funcName () { } var funcName = function funcName () { }; funcName ();
[强迫] ,
和 ;
前不许可有空格。
示例:
javascript
// good callFunc(a, b); // bad callFunc(a , b) ;
[强迫] 在函数挪用、函数声明、括号表达式、属性接见、if / for / while / switch / catch
等语句中,()
和 []
内紧贴括号部份不许可有空格。
示例:
javascript
// 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-- ) { }
[强迫] 单行声明的数组与对象,假如包括元素,{}
和 []
内紧贴括号部份不许可包括空格。
诠释:
声明包括元素的数组与对象,只要当内部元素的情势较为简朴时,才许可写在一行。元素庞杂的状况,照样应该换行誊写。
示例:
javascript
// 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
个字符。
诠释:
超长的不可分割的代码许可破例,比方庞杂的正则表达式。长字符串不在破例之列。
[强迫] 运算符处换行时,运算符必需在新行的行首。
示例:
javascript
// 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语句等场景中,不许可在 ,
或 ;
前换行。
示例:
javascript
// 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 );
[发起] 差别行动或逻辑的语句集,运用空行离隔,更容易浏览。
示例:
javascript
// 仅为按逻辑换行的示例,不代表setStyle的最优完成 function setStyle(element, property, value) { if (element == null) { return; } element.style[property] = value; }
[发起] 在语句的行长度凌驾 120
时,依据逻辑前提合理缩进。
示例:
javascript
// 较庞杂的逻辑前提组合,将每一个前提自力一行,逻辑运算符安排在行首举行分开,或将部份逻辑按逻辑组合举行分开。 // 发起最终将右括号 ) 与左大括号 { 放在自力一行,保证与 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
等语句,引荐运用在 }
号后增加一个换行 的作风,使代码条理组织更清楚,浏览性更好。
示例:
javascript
if (condition) { // some statements; } else { // some statements; } try { // some statements; } catch (ex) { // some statements; }
2.2.4 语句
[强迫] 不得省略语句完毕的分号。
[强迫] 在 if / else / for / do / while
语句中,纵然只要一行,也不得省略块 {...}
。
示例:
javascript
// good if (condition) { callFunc(); } // bad if (condition) callFunc(); if (condition) callFunc();
[强迫] 函数定义完毕不许可增加分号。
示例:
javascript
// good function funcName() { } // bad function funcName() { }; // 假如是函数表达式,分号是不许可省略的。 var funcName = function () { };
[强迫] IIFE
必需在函数表达式外增加 (
,非 IIFE
不得在函数表达式外增加 (
。
诠释:
IIFE = Immediately-Invoked Function Expression.
分外的 ( 可以让代码在浏览的一最先便可以推断函数是不是立即被挪用,进而邃晓接下来代码的用处。而不是一向拖到底部才豁然开朗。
示例:
javascript
// good var task = (function () { // Code return result; })(); var func = function () { }; // bad var task = function () { // Code return result; }(); var func = (function () { });
2.3 定名
[强迫] 变量
运用 Camel定名法
。
示例:
javascript
var loadingModules = {};
[强迫] 常量
运用 悉数字母大写,单词间下划线分开
的定名体式格局。
示例:
javascript
var HTML_ENTITY = {};
[强迫] 函数
运用 Camel定名法
。
示例:
javascript
function stringFormat(source) { }
[强迫] 函数的 参数
运用 Camel定名法
。
示例:
javascript
function hear(theBells) { }
[强迫] 类
运用 Pascal定名法
。
示例:
javascript
function TextNode(options) { }
[强迫] 类的 要领 / 属性
运用 Camel定名法
。
示例:
javascript
function TextNode(value, engine) { this.value = value; this.engine = engine; } TextNode.prototype.clone = function () { return this; };
[强迫] 罗列变量
运用 Pascal定名法
,罗列的属性
运用 悉数字母大写,单词间下划线分开
的定名体式格局。
示例:
javascript
var TargetState = { READING: 1, READED: 2, APPLIED: 3, READY: 4 };
[强迫] 定名空间
运用 Camel定名法
。
示例:
javascript
equipments.heavyWeapons = {};
[强迫] 由多个单词构成的缩写词,在定名中,依据当前定名法和涌现的位置,一切字母的大小写与首字母的大小写坚持一致。
示例:
javascript
function XMLParser() { } function insertHTML(element, html) { } var httpRequest = new HTTPRequest();
[强迫] 类名
运用 名词
。
示例:
javascript
function Engine(options) { }
[发起] 函数名
运用 动宾短语
。
示例:
javascript
function getStyle(element) { }
[发起] boolean
范例的变量运用 is
或 has
开首。
示例:
javascript
var isReady = false; var hasMoreCommands = false;
[发起] Promise对象
用 动宾短语的举行时
表达。
示例:
javascript
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
标识文件申明。
示例:
javascript
/** * @file Describe the file */
[发起] 文件解释中可以用 @author
标识开辟者信息。
诠释:
开辟者信息可以表现开辟人员对文件的孝敬,而且可以让遇到题目或愿望相识相干信息的人找到保护人。平常状况文件在被建立时标识的是建立者。跟着项目标希望,越来越多的人到场,介入这个文件的开辟,新的作者应该被到场 @author
标识。
@author
标识具有多人时,准绳是根据 义务
举行排序。平常的说就是假如有题目,就是找第一个人应该比找第二个人有用。比方文件的建立者由于种种缘由,模块移交给了其他人或其他团队,厥后由于新增需求,其他人在新增代码时,增加 @author
标识应该把本身的名字增加在建立人的前面。
@author
中的名字不许可被删除。任何劳动成果都应该被尊敬。
营业项目中,一个文件可以被多人频仍修改,而且每一个人的保护时刻都可以不会很长,不发起为文件增添 @author
标识。经由历程版本掌握系统追踪变动,按营业逻辑单位肯定模块的保护义务人,经由历程文档与wiki跟踪和查询,是更好的义务治理体式格局。
关于营业逻辑无关的手艺型基本项目,迥殊是开源的大众项目,应运用 @author
标识。
示例:
javascript
/** * @file Describe the file * @author author-name(mail-name@domain.com) * author-name2(mail-name2@domain.com) */
2.4.6 定名空间解释
[发起] 定名空间运用 @namespace
标识。
示例:
javascript
/** * @namespace */ var util = {};
2.4.7 类解释
[发起] 运用 @class
标记类或组织函数。
诠释:
关于运用对象 constructor
属性来定义的组织函数,可以运用 @constructor
来标记。
示例:
javascript
/** * 形貌 * * @class */ function Developer() { // constructor body }
[发起] 运用 @extends
标记类的继续信息。
示例:
javascript
/** * 形貌 * * @class * @extends Developer */ function Fronteer() { Developer.call(this); // constructor body } util.inherits(Fronteer, Developer);
[强迫] 运用包装体式格局扩大类成员时, 必需经由历程 @lends
举行从新指向。
诠释:
没有 @lends
标记将没法为该类天生包括扩大类成员的文档。
示例:
javascript
/** * 类形貌 * * @class * @extends Developer */ function Fronteer() { Developer.call(this); // constructor body } util.extend( Fronteer.prototype, /** @lends Fronteer.prototype */{ _getLevel: function () { // TODO } } );
[强迫] 类的属性或要领等成员信息运用 @public
/ @protected
/ @private
中的恣意一个,指明可接见性。
诠释:
天生的文档中将有可接见性的标记,防止用户直接运用非 public
的属性或要领。
示例:
javascript
/** * 类形貌 * * @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
标识。
示例:
javascript
/** * 函数形貌 * * @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
标识。
示例:
javascript
/** * 函数形貌 * * @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
标识事宜,事宜参数的标识与要领形貌的参数标识雷同。
示例:
javascript
/** * 值变动时触发 * * @event * @param {Object} e e形貌 * @param {string} e.before before形貌 * @param {string} e.after after形貌 */ onchange: function (e) { }
[强迫] 在会播送事宜的函数前运用 @fires
标识播送的事宜,在播送事宜代码前运用 @event
标识事宜。
[发起] 关于事宜对象的解释,运用 @param
标识,天生文档时可读性更好。
示例:
javascript
/** * 点击处置惩罚 * * @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
标记,并包括申明和范例信息。
示例:
javascript
/** * 常量申明 * * @const * @type {string} */ var REQUEST_URL = 'myurl.do';
2.4.11 庞杂范例解释
[发起] 关于范例未定义的庞杂组织的解释,可以运用 @typedef
标识来定义。
示例:
javascript
// `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: 前缀。
示例:
javascript
define( function (require) { /** * foo description * * @exports Foo */ var foo = { // TODO }; /** * baz description * * @return {boolean} return description */ foo.baz = function () { // TODO }; return foo; } );
也可以在 exports 变量前运用 @module 标识:
javascript
define( function (require) { /** * module description. * * @module foo */ var exports = {}; /** * bar description * */ exports.bar = function () { // TODO }; return exports; } );
假如直接运用 factory 的 exports 参数,还可以:
javascript
/** * module description. * * @module */ define( function (require, exports) { /** * bar description * */ exports.bar = function () { // TODO }; return exports; } );
[强迫] 关于已运用 @module
标识为 AMD模块 的援用,在 namepaths
中必需增添 module:
作前缀。
诠释:
namepaths 没有 module: 前缀时,天生的文档中将没法准确天生链接。
示例:
javascript
/** * 点击处置惩罚 * * @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
标识构建函数。
示例:
javascript
/** * A module representing a jacket. * @module jacket */ define( function () { /** * @class * @alias module:jacket */ var Jacket = function () { }; return Jacket; } );
[发起] 多模块定义时,可以运用 @exports
标识各个模块。
示例:
javascript
// one module define('html/utils', /** * Utility functions to ease working with DOM elements. * @exports html/utils */ function () { var exports = { }; return exports; } ); // another module define('tag', /** @exports tag */ function () { var exports = { }; return exports; } );
[发起] 关于 exports 为 Object 的模块,可以运用@namespace
标识。
诠释:
运用 @namespace 而不是 @module 或 @exports 时,对模块的援用可以省略 module: 前缀。
[发起] 关于 exports 为类名的模块,运用 @class
和 @exports
标识。
示例:
javascript
// 只运用 @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 细节解释
关于内部完成、不轻易邃晓的逻辑申明、择要信息等,我们可以须要编写细节解释。
[发起] 细节解释遵照单行解释的花样。申明必需换行时,每行是一个单行解释的肇端。
示例:
javascript
function foo(p1, p2) { // 这里对细致内部逻辑举行申明 // 申明太长须要换行 for (...) { .... } }
[强迫] 偶然我们会运用一些特别标记举行申明。特别标记必需运用单行解释的情势。下面列举了一些常常使用标记:
诠释:
- TODO: 有功用待完成。此时须要对将要完成的功用举行简朴申明。
- FIXME: 该处代码运转没题目,但可以由于时刻赶或许其他缘由,须要修改。此时须要对怎样修改举行简朴申明。
- HACK: 为修改某些题目而写的不太好或许运用了某些诡异手腕的代码。此时须要对思绪或诡异手腕举行形貌。
- XXX: 该处存在圈套。此时须要对圈套举行形貌。
3 言语特征
3.1 变量
[强迫] 变量在运用前必需经由历程 var
定义。
诠释:
不经由历程 var 定义变量将致使变量污染全局环境。
示例:
javascript
// good var name = 'MyName'; // bad name = 'MyName';
[强迫] 每一个 var
只能声明一个变量。
诠释:
一个 var 声明多个变量,轻易致使较长的行长度,而且在修改时轻易构成逗号和分号的殽杂。
示例:
javascript
// good var hangModules = []; var missModules = []; var visited = {}; // bad var hangModules = [], missModules = [], visited = {};
[强迫] 变量必需 即用即声明
,不得在函数或别的情势的代码块肇端位置一致声明一切变量。
诠释:
变量声明与运用的间隔越远,涌现的跨度越大,代码的浏览与保护本钱越高。虽然JavaScript的变量是函数作用域,照样应该依据编程中的企图,削减变量涌现的间隔空间。
示例:
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
。
诠释:
运用 === 可以防止即是推断中隐式的范例转换。
示例:
javascript
// good if (age === 30) { // ...... } // bad if (age == 30) { // ...... }
[发起] 只管运用简约的表达式。
示例:
javascript
// 字符串为空 // good if (!name) { // ...... } // bad if (name === '') { // ...... }
javascript
// 字符串非空 // good if (name) { // ...... } // bad if (name !== '') { // ...... }
javascript
// 数组非空 // good if (collection.length) { // ...... } // bad if (collection.length > 0) { // ...... }
javascript
// 布尔不成立 // good if (!notTrue) { // ...... } // bad if (notTrue === false) { // ...... }
javascript
// null 或 undefined // good if (noValue == null) { // ...... } // bad if (noValue === null || typeof noValue === 'undefined') { // ...... }
[发起] 按实行频次分列分支的递次。
诠释:
按实行频次分列分支的递次优点是:
- 浏览的人轻易找到最罕见的状况,增添可读性。
- 进步实行效力。
[发起] 关于雷同变量或表达式的多值前提,用 switch
替换 if
。
示例:
javascript
// 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
。
示例:
javascript
// good function getName() { if (name) { return name; } return 'unnamed'; } // bad function getName() { if (name) { return name; } else { return 'unnamed'; } }
3.3 轮回
[发起] 不要在轮回体中包括函数表达式,事先将函数提取到轮回体外。
诠释:
轮回体中的函数表达式,运转历程当中会天生轮回次数个函数对象。
示例:
javascript
// 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 () {}); }
[发起] 对轮回内屡次运用的稳定值,在轮回外用变量缓存。
示例:
javascript
// 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 能有用进步递次机能。
示例:
javascript
for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; // ...... }
[发起] 对有序鸠合举行递次无关的遍用时,运用逆序遍历。
诠释:
逆序遍历可以勤俭变量,代码比较优化。
示例:
javascript
var len = elements.length; while (len--) { var element = elements[len]; // ...... }
3.4 范例
3.4.1 范例检测
[发起] 范例检测优先运用 typeof
。对象范例检测运用 instanceof
。null
或 undefined
的检测运用 == null
。
示例:
javascript
// 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
时,运用 + ''
。
示例:
javascript
// good num + ''; // bad new String(num); num.toString(); String(num);
[发起] 转换成 number
时,平常运用 +
。
示例:
javascript
// good +str; // bad Number(str);
[发起] string
转换成 number
,要转换的字符串末端包括非数字并希冀疏忽时,运用 parseInt
。
示例:
javascript
var width = '200px'; parseInt(width, 10);
[强迫] 运用 parseInt
时,必需指定进制。
示例:
javascript
// good parseInt(str, 10); // bad parseInt(str);
[发起] 转换成 boolean
时,运用 !!
。
示例:
javascript
var num = 3.14; !!num;
[发起] number
去除小数点,运用 Math.floor / Math.round / Math.ceil
,不运用 parseInt
。
示例:
javascript
// good var num = 3.14; Math.ceil(num); // bad var num = 3.14; parseInt(num, 10);
3.5 字符串
[强迫] 字符串开首和完毕运用单引号 '
。
诠释:
- 输入单引号不须要按住 shift,轻易输入。
- 现实运用中,字符串经常常使用来拼接 HTML。为轻易 HTML 中包括双引号而不须要转义写法。
示例:
javascript
var str = '我是一个字符串'; var html = '<div class="cls">拼接HTML可以省去双引号转义</div>';
[发起] 运用 数组
或 +
拼接字符串。
诠释:
- 运用 + 拼接字符串,假如拼接的悉数是 StringLiteral,压缩东西可以对其举行自动兼并的优化。所以,静态字符串发起运用 + 拼接。
- 在当代浏览器下,运用 + 拼接字符串,机能较数组的体式格局要高。
- 如须要统筹老旧浏览器,应只管运用数组拼接字符串。
示例:
javascript
// 运用数组拼接字符串 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
。
示例:
javascript
// good var obj = {}; // bad var obj = new Object();
[强迫] 对象建立时,假如一个对象的一切 属性
均可以不增加引号,则一切 属性
不得增加引号。
示例:
javascript
var info = { name: 'someone', age: 28 };
[强迫] 对象建立时,假如任何一个 属性
须要增加引号,则一切 属性
必需增加 '
。
诠释:
假如属性不相符 Identifier 和 NumberLiteral 的情势,就须要以 StringLiteral 的情势供应。
示例:
javascript
// good var info = { 'name': 'someone', 'age': 28, 'more-info': '...' }; // bad var info = { name: 'someone', age: 28, 'more-info': '...' };
[强迫] 不许可修改和扩大任何原生对象和宿主对象的原型。
示例:
javascript
// 以下行动相对制止 String.prototype.trim = function () { };
[发起] 属性接见时,只管运用 .
。
诠释:
属性名相符 Identifier 的请求,便可以经由历程 .
来接见,不然就只能经由历程 [expr]
体式格局接见。
平常在 JavaScript 中声明的对象,属性定名是运用 Camel 定名法,用 .
来接见更清楚简约。部份特别的属性(比方来自后端的JSON),可以采纳不寻常的定名体式格局,可以经由历程 [expr]
体式格局接见。
示例:
javascript
info.age; info['more-info'];
[发起] for in
遍历对象时, 运用 hasOwnProperty
过滤掉原型中的属性。
示例:
javascript
var newInfo = {}; for (var key in info) { if (info.hasOwnProperty(key)) { newInfo[key] = info[key]; } }
3.7 数组
[强迫] 运用数组字面量 []
建立新数组,除非想要建立的是指定长度的数组。
示例:
javascript
// good var arr = []; // bad var arr = new Array();
[强迫] 遍历数组不运用 for in
。
诠释:
数组对象可以存在数字之外的属性, 这类状况下 for in 不会获得准确效果.
示例:
javascript
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
行之内。
诠释:
将过量的逻辑单位混在一个大函数中,易致使难以保护。一个清楚易懂的函数应该完成单一的逻辑单位。庞杂的操纵应进一步抽取,经由历程函数的挪用来表现流程。
特定算法等不可分割的逻辑许可破例。
示例:
javascript
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 参数通报。
以下函数:
javascript
/** * 移除某个元素 * * @param {Node} element 须要移除的元素 * @param {boolean} removeEventListeners 是不是同时将一切注册在元素上的事宜移除 */ function removeElement(element, removeEventListeners) { element.parent.removeChild(element); if (removeEventListeners) { element.clearEventListeners(); } }
可以转换为下面的署名:
javascript
/** * 移除某个元素 * * @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 效应
。
诠释:
在援用函数外部变量时,函数实行时外部变量的值由运转时决议而非定义时,最典范的场景以下:
javascript
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 效应 。处理的体式格局是经由历程分外加上一层闭包函数,将须要的外部变量作为参数通报来消除变量的绑定关联:
javascript
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()
的情势。
示例:
javascript
var emptyFunction = function () {};
[发起] 关于机能有高请求的场所,发起存在一个空函数的常量,供多处运用同享。
示例:
javascript
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 修改。
示例:
javascript
/** * 构建类之间的继续关联 * * @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
的准确性。
示例:
javascript
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'); };
[发起] 属性在组织函数中声明,要领在原型中声明。
诠释:
原型对象的成员被一切实例同享,能勤俭内存占用。所以编码时我们应该恪守如许的准绳:原型对象包括递次不会修改的成员,如要领函数或设置项。
javascript
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。
示例:
javascript
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 块。
示例:
javascript
try { delete o.x; } catch (deleteError) { o.x = null; }
3.10.5 对象属性
[发起] 防止修改外部传入的对象。
诠释:
JavaScript 因其脚本言语的动态特征,当一个对象未被 seal 或 freeze 时,可以恣意增加、删除、修改属性值。
然则随便地对 非本身掌握的对象 举行修改,很轻易构成代码在不可预知的状况下涌现题目。因而,设想优越的组件、函数应该防止对外部传入的对象的修改。
下面代码的 selectNode 要领修改了由外部传入的 datasource 对象。假如 datasource 用在别的场所(如另一个 Tree 实例)下,会构成状况的杂沓。
javascript
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 作任何修改。
javascript
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 范例,提早作好转换,而不是希冀对象、函数可以处置惩罚多范例的值。