Open WarpPrism opened 7 years ago
本文档的目标是使 JavaScript 代码风格保持一致,容易被理解和被维护。
虽然本文档是针对 JavaScript 设计的,但是在使用各种 JavaScript 的预编译语言时(如 TypeScript 等)时,适用的部分也应尽量遵循本文档的约定。
[建议] JavaScript 文件使用无 BOM 的 UTF-8 编码。
[强制] 使用 4 个空格做为一个缩进层级,不允许使用 2 个空格 或 tab 字符。
[强制] 二元运算符两侧必须有一个空格,一元运算符与操作对象之间不允许有空格。 示例:
var a = !arr.length; a++; a = b + c;
[强制] 用作代码块起始的左花括号 { 前必须有一个空格。
{
// good if (condition) { } while (condition) { } function funcName() { } // bad if (condition){ } while (condition){ } function funcName(){ }
[强制] if / else / for / while / function / switch / do / try / catch / finally 关键字后,必须有一个空格。 示例:
if / else / for / while / function / switch / do / try / catch / finally
// good if (condition) { } while (condition) { } (function () { })(); // bad if(condition) { } while(condition) { } (function() { })();
[强制] 在对象创建时,属性中的 : 之后必须有空格,: 之前不允许有空格。 示例:
// good var obj = { a: 1, b: 2, c: 3 }; // bad var obj = { a : 1, b:2, c :3 };
[强制] , 和 ; 前不允许有空格。如果不位于行尾,, 和 ; 后必须跟一个空格。 示例:
// good callFunc(a, b); // bad callFunc(a , b) ;
[强制] 在函数调用、函数声明、括号表达式、属性访问、if / for / while / switch / catch 等语句中,() 和 [] 内紧贴括号部分不允许有空格。
if / for / while / switch / catch
()
[]
示例:
// 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;
[强制] 在函数声明、函数表达式、函数调用、对象创建、数组创建、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 );
[建议] 在语句的行长度超过 120 时,根据逻辑条件合理缩进。
// 按一定长度截断字符串,并使用 + 运算符进行连接。 // 分隔字符串尽量按语义进行,如不要在一个完整的名词中间断开。 // 特别的,对于 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(''); // 当函数调用时,如果有一个或以上参数跨越多行,应当每一个参数独立一行。 // 这通常出现在匿名函数或者对象初始化等作为参数时,如 `setTimeout` 函数等。 setTimeout( function () { alert('hello'); }, 200 ); // 链式调用较长时采用缩进进行调整。 $('#items') .find('.selected') .highlight() .end(); // 三元运算符由3部分组成,因此其换行应当根据每个部分的长度不同,形成不同的情况。 var result = thisIsAVeryVeryLongCondition ? resultA : resultB; var result = condition ? thisIsAVeryVeryLongResult : resultB;
[强制] 不得省略语句结束的分号。 [强制] 在 if / else / for / do / while 语句中,即使只有一行,也不得省略块 {...}。
if / else / for / do / while
// good if (condition) { callFunc(); } // bad if (condition) callFunc(); if (condition) callFunc();
[强制] 变量,函数,函数的参数,方法/属性 使用 Camel命名法。 示例:
变量
函数
函数的参数
方法/属性
Camel
var loadingModules = {}; function stringFormat(sourceString) { }
[强制] 常量使用 全部字母大写,单词间下划线分隔 的命名方式。 示例:
常量
var HTML_ENTITY = {};
[强制] 类,枚举变量 使用 Pascal命名法。枚举的属性 使用 全部字母大写,单词间下划线分隔 的命名方式。 示例:
类
枚举变量
Pascal
var TargetState = { READING: 1, READED: 2, APPLIED: 3, READY: 4 };
[建议] Sublime Text的同学,可以安装docBlkr文档编写插件。 [强制] 为了便于代码阅读和自文档化,以下内容必须包含以 /*.../ 形式的块注释中。
docBlkr
文件 namespace 类 函数或方法 类属性 事件 全局变量 常量 AMD 模块 [强制] 文档注释前必须空一行。
[建议] 自文档化的文档说明 what,而不是 how。
/** * 函数描述 * * @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 }; }
原则上不建议使用全局变量,对于已有的全局变量或第三方框架引入的全局变量,需要根据检查工具的语法标识。
[强制] 每个 var 只能声明一个变量。
一个 var 声明多个变量,容易导致较长的行长度,并且在修改时容易造成逗号和分号的混淆。
// good var hangModules = []; var missModules = []; var visited = {}; // bad var hangModules = [], missModules = [], visited = {};
[强制] 在 Equality Expression 中使用类型严格的 ===。仅当判断 null 或 undefined 时,允许使用 == null。
使用 === 可以避免等于判断中隐式的类型转换。
[建议] 对有序集合进行遍历时,缓存 length。
虽然现代浏览器都对数组长度进行了缓存,但对于一些宿主对象和老旧浏览器的数组对象,在每次 length 访问时会动态计算元素个数,此时缓存 length 能有效提高程序性能。
for (var i = 0, len = elements.length; i < len; i++) { var element = elements[i]; // ...... }
[建议] 转换成 string 时,使用 + ''。
+ ''
// good num + ''; // bad new String(num); num.toString(); String(num);
[建议] 转换成 number 时,通常使用 +。
number
+
// good +str; // bad Number(str);
[建议] string 转换成 number,要转换的字符串结尾包含非数字并期望忽略时,使用 parseInt。
string
parseInt
var width = '200px'; parseInt(width, 10);
[强制] 使用 parseInt 时,必须指定进制。
// good parseInt(str, 10); // bad parseInt(str);
[建议] 转换成 boolean 时,使用 !!。
boolean
!!
var num = 3.14; !!num;
[建议] number 去除小数点,使用 Math.floor / Math.round / Math.ceil,不使用 parseInt。
Math.floor / Math.round / Math.ceil
// good var num = 3.14; Math.ceil(num); // bad var num = 3.14; parseInt(num, 10);
[强制] 字符串开头和结束使用单引号 '。
输入单引号不需要按住 shift,方便输入。 实际使用中,字符串经常用来拼接 HTML。为方便 HTML 中包含双引号而不需要转义写法。 示例:
var str = '我是一个字符串'; var html = '<div class="cls">拼接HTML可以省去双引号转义</div>';
[强制] 避免使用直接 eval 函数。
直接 eval,指的是以函数方式调用 eval 的调用方法。直接 eval 调用执行代码的作用域为本地作用域,应当避免。
如果有特殊情况需要使用直接 eval,需在代码中用详细的注释说明为何必须使用直接 eval,不能使用其它动态执行代码的方式,同时需要其他资深工程师进行 Code Review。
[建议] 尽量避免使用 eval 函数。
[强制] 使用 AMD 作为模块定义。
AMD 作为由社区认可的模块定义形式,提供多种重载提供灵活的使用方式,并且绝大多数优秀的 Library 都支持 AMD,适合作为规范。
目前,比较成熟的 AMD Loader 有:
官方实现的 requirejs
[强制] 模块 id 必须符合标准。
模块 id 必须符合以下约束条件:
类型为 string,并且是由 / 分割的一系列 terms 来组成。例如:this/is/a/module。 term 应该符合 [a-zA-Z0-9_-]+ 规则。 不应该有 .js 后缀。 跟文件的路径保持一致。
/
this/is/a/module
[a-zA-Z0-9_-]+
.js
[建议] 定义模块时不要指明 id 和 dependencies。
id
dependencies
在 AMD 的设计思想里,模块名称是和所在路径相关的,匿名的模块更利于封包和迁移。模块依赖应在模块定义内部通过 local require 引用。
所以,推荐使用 define(factory) 的形式进行模块定义。
define(factory)
define( function (require) { } );
[建议] 使用 return 来返回模块定义。
使用 return 可以减少 factory 接收的参数(不需要接收 exports 和 module),在没有 AMD Loader 的场景下也更容易进行简单的处理来伪造一个 Loader。
return
factory
exports
module
define( function (require) { var exports = {}; // ... return exports; } );
[强制] 全局运行环境中,require 必须以 async require 形式调用。
require
async require
模块的加载过程是异步的,同步调用并无法保证得到正确的结果。
// good require(['foo'], function (foo) { }); // bad var foo = require('foo');
如果你打算编写通用模块(支持Common.js、AMD等),你可能需要参考一下如何用UMD来包装你的模块:
UMD
[建议] 操作 DOM 时,尽量减少页面 reflow。
页面 reflow 是非常耗时的行为,非常容易导致性能瓶颈。下面一些场景会触发浏览器的reflow:
DOM元素的添加、修改(内容)、删除。 应用新的样式或者修改任何影响元素布局的属性。 Resize浏览器窗口、滚动页面。 读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)) 。 [建议] 尽量减少 DOM 操作。
offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)
DOM 操作也是非常耗时的一种操作,减少 DOM 操作有助于提高性能。举一个简单的例子,构建一个列表。我们可以用两种方式:
在循环体中 createElement 并 append 到父元素中。 在循环体中拼接 HTML 字符串,循环结束后写父元素的 innerHTML。 第一种方法看起来比较标准,但是每次循环都会对 DOM 进行操作,性能极低。在这里推荐使用第二种方法。
createElement
append
innerHTML
写的非常好, 最后尽量减少DOM操作,好难啊。 拼写HTML字符串 然后直接用innerHTML这个好
有一个问题, 读取元素的某些属性为什么会触发reflow?
@hi363138911 Reflow(重流)的定义就是计算DOM节点在页面上展示的位置和空间等等。而读取offsetLeft/Top/Width/Height等属性,其背后进行的正是计算节点和其父节点位置,宽高等属性,所以会触发reflow,这是我的理解。
文档说明
本文档的目标是使 JavaScript 代码风格保持一致,容易被理解和被维护。
虽然本文档是针对 JavaScript 设计的,但是在使用各种 JavaScript 的预编译语言时(如 TypeScript 等)时,适用的部分也应尽量遵循本文档的约定。
1. 代码风格
1.1 文件
[建议] JavaScript 文件使用无 BOM 的 UTF-8 编码。
1.2 结构
1.2.1 缩进
[强制] 使用 4 个空格做为一个缩进层级,不允许使用 2 个空格 或 tab 字符。
1.2.2 空格
[强制] 二元运算符两侧必须有一个空格,一元运算符与操作对象之间不允许有空格。 示例:
[强制] 用作代码块起始的左花括号
{
前必须有一个空格。[强制]
if / else / for / while / function / switch / do / try / catch / finally
关键字后,必须有一个空格。 示例:[强制] 在对象创建时,属性中的 : 之后必须有空格,: 之前不允许有空格。 示例:
[强制] , 和 ; 前不允许有空格。如果不位于行尾,, 和 ; 后必须跟一个空格。 示例:
[强制] 在函数调用、函数声明、括号表达式、属性访问、
if / for / while / switch / catch
等语句中,()
和[]
内紧贴括号部分不允许有空格。示例:
[强制] 单行声明的数组与对象,如果包含元素,
{}
和[]
内紧贴括号部分不允许包含空格。解释:
声明包含元素的数组与对象,只有当内部元素的形式较为简单时,才允许写在一行。元素复杂的情况,还是应该换行书写。
示例:
[强制] 行尾不得有多余的空格。
1.2.3 换行
[强制] 每个独立语句结束后必须换行。
[强制] 每行不得超过 120 个字符。
解释:
超长的不可分割的代码允许例外,比如复杂的正则表达式。长字符串不在例外之列。
[强制] 运算符处换行时,运算符必须在新行的行首。
示例:
[强制] 在函数声明、函数表达式、函数调用、对象创建、数组创建、for 语句等场景中,不允许在 , 或 ; 前换行。 示例:
[建议] 在语句的行长度超过 120 时,根据逻辑条件合理缩进。
示例:
1.2.4 语句
[强制] 不得省略语句结束的分号。 [强制] 在
if / else / for / do / while
语句中,即使只有一行,也不得省略块 {...}。示例:
1.3 命名
[强制]
变量
,函数
,函数的参数
,方法/属性
使用Camel
命名法。 示例:[强制]
常量
使用 全部字母大写,单词间下划线分隔 的命名方式。 示例:[强制]
类
,枚举变量
使用Pascal
命名法。枚举的属性 使用 全部字母大写,单词间下划线分隔 的命名方式。 示例:1.4 注释
1.4.1 文档化注释
[建议] Sublime Text的同学,可以安装
docBlkr
文档编写插件。 [强制] 为了便于代码阅读和自文档化,以下内容必须包含以 /*.../ 形式的块注释中。解释:
文件 namespace 类 函数或方法 类属性 事件 全局变量 常量 AMD 模块 [强制] 文档注释前必须空一行。
[建议] 自文档化的文档说明 what,而不是 how。
示例:
2 语言特征
2.1 变量
原则上不建议使用全局变量,对于已有的全局变量或第三方框架引入的全局变量,需要根据检查工具的语法标识。
[强制] 每个 var 只能声明一个变量。
解释:
一个 var 声明多个变量,容易导致较长的行长度,并且在修改时容易造成逗号和分号的混淆。
示例:
2.2 条件
[强制] 在 Equality Expression 中使用类型严格的 ===。仅当判断 null 或 undefined 时,允许使用 == null。
解释:
使用 === 可以避免等于判断中隐式的类型转换。
2.3 循环
[建议] 对有序集合进行遍历时,缓存 length。
解释:
虽然现代浏览器都对数组长度进行了缓存,但对于一些宿主对象和老旧浏览器的数组对象,在每次 length 访问时会动态计算元素个数,此时缓存 length 能有效提高程序性能。
示例:
2.4 类型
2.4.1 类型转换
[建议] 转换成 string 时,使用
+ ''
。示例:
[建议] 转换成
number
时,通常使用+
。示例:
[建议]
string
转换成number
,要转换的字符串结尾包含非数字并期望忽略时,使用parseInt
。示例:
[强制] 使用
parseInt
时,必须指定进制。示例:
[建议] 转换成
boolean
时,使用!!
。示例:
[建议]
number
去除小数点,使用Math.floor / Math.round / Math.ceil
,不使用parseInt
。示例:
2.5 字符串
[强制] 字符串开头和结束使用单引号 '。
解释:
输入单引号不需要按住 shift,方便输入。 实际使用中,字符串经常用来拼接 HTML。为方便 HTML 中包含双引号而不需要转义写法。 示例:
2.6 动态特征
2.6.1 eval
[强制] 避免使用直接 eval 函数。
解释:
直接 eval,指的是以函数方式调用 eval 的调用方法。直接 eval 调用执行代码的作用域为本地作用域,应当避免。
如果有特殊情况需要使用直接 eval,需在代码中用详细的注释说明为何必须使用直接 eval,不能使用其它动态执行代码的方式,同时需要其他资深工程师进行 Code Review。
[建议] 尽量避免使用 eval 函数。
3 浏览器环境
3.1 模块化
3.1.1 AMD
[强制] 使用 AMD 作为模块定义。
解释:
AMD 作为由社区认可的模块定义形式,提供多种重载提供灵活的使用方式,并且绝大多数优秀的 Library 都支持 AMD,适合作为规范。
目前,比较成熟的 AMD Loader 有:
官方实现的 requirejs
[强制] 模块 id 必须符合标准。
解释:
模块 id 必须符合以下约束条件:
类型为 string,并且是由
/
分割的一系列 terms 来组成。例如:this/is/a/module
。 term 应该符合[a-zA-Z0-9_-]+
规则。 不应该有.js
后缀。 跟文件的路径保持一致。3.1.2 define
[建议] 定义模块时不要指明
id
和dependencies
。解释:
在 AMD 的设计思想里,模块名称是和所在路径相关的,匿名的模块更利于封包和迁移。模块依赖应在模块定义内部通过 local require 引用。
所以,推荐使用
define(factory)
的形式进行模块定义。示例:
[建议] 使用 return 来返回模块定义。
解释:
使用
return
可以减少factory
接收的参数(不需要接收exports
和module
),在没有 AMD Loader 的场景下也更容易进行简单的处理来伪造一个 Loader。示例:
3.1.3 require
[强制] 全局运行环境中,
require
必须以async require
形式调用。解释:
模块的加载过程是异步的,同步调用并无法保证得到正确的结果。
示例:
3.1.4 UMD
如果你打算编写通用模块(支持Common.js、AMD等),你可能需要参考一下如何用UMD来包装你的模块:
UMD
3.2 DOM
3.2.1 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 进行操作,性能极低。在这里推荐使用第二种方法。