文档说明
本文档的目标是使 JavaScript 代码风格保持一致,容易被理解和被维护。
虽然本文档是针对 JavaScript 设计的,但是在使用各种 JavaScript 的预编译语言时(如 TypeScript 等)时,适用的部分也应尽量遵循本文档的约定。
1. 代码风格
1.1 文件
[建议] JavaScript 文件使用无 BOM 的 UTF-8 编码。
1.2 结构
1.2.1 缩进
[强制] 使用 4 个空格做为一个缩进层级,不允许使用 2 个空格 或 tab 字符。
1.2.2 空格
[强制] 二元运算符两侧必须有一个空格,一元运算符与操作对象之间不允许有空格。
示例:
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 关键字后,必须有一个空格。
示例:
// 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 等语句中,() 和 [] 内紧贴括号部分不允许有空格。
示例:
// 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};
[强制] 行尾不得有多余的空格。
1.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
);
[建议] 在语句的行长度超过 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;
1.2.4 语句
[强制] 不得省略语句结束的分号。
[强制] 在 if / else / for / do / while 语句中,即使只有一行,也不得省略块 {...}。
示例:
// good
if (condition) {
callFunc();
}
// bad
if (condition) callFunc();
if (condition)
callFunc();
1.3 命名
[强制] 变量,函数,函数的参数,方法/属性 使用 Camel命名法。
示例:
var loadingModules = {};
function stringFormat(sourceString) {
}
[强制] 常量使用 全部字母大写,单词间下划线分隔 的命名方式。
示例:
[强制] 类,枚举变量 使用 Pascal命名法。枚举的属性 使用 全部字母大写,单词间下划线分隔 的命名方式。
示例:
var TargetState = {
READING: 1,
READED: 2,
APPLIED: 3,
READY: 4
};
1.4 注释
1.4.1 文档化注释
[建议] Sublime Text的同学,可以安装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
};
}
2 语言特征
2.1 变量
原则上不建议使用全局变量,对于已有的全局变量或第三方框架引入的全局变量,需要根据检查工具的语法标识。
[强制] 每个 var 只能声明一个变量。
解释:
一个 var 声明多个变量,容易导致较长的行长度,并且在修改时容易造成逗号和分号的混淆。
示例:
// good
var hangModules = [];
var missModules = [];
var visited = {};
// bad
var hangModules = [],
missModules = [],
visited = {};
2.2 条件
[强制] 在 Equality Expression 中使用类型严格的 ===。仅当判断 null 或 undefined 时,允许使用 == null。
解释:
使用 === 可以避免等于判断中隐式的类型转换。
2.3 循环
[建议] 对有序集合进行遍历时,缓存 length。
解释:
虽然现代浏览器都对数组长度进行了缓存,但对于一些宿主对象和老旧浏览器的数组对象,在每次 length 访问时会动态计算元素个数,此时缓存 length 能有效提高程序性能。
示例:
for (var i = 0, len = elements.length; i < len; i++) {
var element = elements[i];
// ......
}
2.4 类型
2.4.1 类型转换
[建议] 转换成 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 时,使用 !!。
示例:
[建议] number 去除小数点,使用 Math.floor / Math.round / Math.ceil,不使用 parseInt。
示例:
// good
var num = 3.14;
Math.ceil(num);
// bad
var num = 3.14;
parseInt(num, 10);
2.5 字符串
[强制] 字符串开头和结束使用单引号 '。
解释:
输入单引号不需要按住 shift,方便输入。
实际使用中,字符串经常用来拼接 HTML。为方便 HTML 中包含双引号而不需要转义写法。
示例:
var str = '我是一个字符串';
var html = '<div class="cls">拼接HTML可以省去双引号转义</div>';
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) 的形式进行模块定义。
示例:
define(
function (require) {
}
);
[建议] 使用 return 来返回模块定义。
解释:
使用 return 可以减少 factory 接收的参数(不需要接收 exports 和 module),在没有 AMD Loader 的场景下也更容易进行简单的处理来伪造一个 Loader。
示例:
define(
function (require) {
var exports = {};
// ...
return exports;
}
);
3.1.3 require
[强制] 全局运行环境中,require 必须以 async require 形式调用。
解释:
模块的加载过程是异步的,同步调用并无法保证得到正确的结果。
示例:
// good
require(['foo'], function (foo) {
});
// bad
var foo = require('foo');
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 进行操作,性能极低。在这里推荐使用第二种方法。
文档说明
本文档的目标是使 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 进行操作,性能极低。在这里推荐使用第二种方法。