JavaScript 编码规范

换行

每行不得超过 120 个字符。运算符处换行时,运算符必须在新行的行首。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
if (user.isAuthenticated()
&& user.isInRole('admin')
&& user.hasAuthority('add-admin')
) {
// 建议最终将右括号 ) 与左大括号 { 放在独立一行,保证与 if 内语句块能容易视觉辨识。
// Code
}

// 不同行为或逻辑的语句集,使用空行隔开,更易阅读。
var result = number1 + number2 + number3
+ number4 + number5;

// 特别的,对于HTML片段的拼接,通过缩进,保持和HTML相同的结构。
var html = '' // 此处用一个空字符串,以便整个HTML片段都在新行严格对齐
+ '<article>'
+ '<h1>Title here</h1>'
+ '<p>This is a paragraph</p>'
+ '<footer>Complete</footer>'
+ '</article>';

// 三元运算符由3部分组成,因此其换行应当根据每个部分的长度不同,形成不同的情况。
var result = thisIsAVeryVeryLongCondition
? resultA : resultB;

// 当参数过多时,将每个参数独立写在一行上,并将结束的右括号 ) 独立一行。
// 所有参数必须增加一个缩进。
foo(
aVeryVeryLongArgument,
anotherVeryLongArgument,
callback
);

// 数组和对象初始化的混用,严格按照每个对象的 { 和结束 } 在独立一行的风格书写。
var array = [
{
// ...
},
{
// ...
}
];

语句

函数定义结束不允许添加分号。

1
2
3
4
// good
function funcName() {
// code
}

命名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// `常量` 使用 `全部字母大写,单词间下划线分隔` 的命名方式。
var HTML_ENTITY = {};

// 枚举变量 使用 Pascal命名法(每一个单字的首字母都大写)。
// 枚举的属性 使用 全部字母大写,单词间下划线分隔 的命名方式。
var TargetState = {
READING: 1,
READED: 2,
APPLIED: 3,
READY: 4
};

// 命名空间 使用 Camel命名法。
equipments.heavyWeapons = {};

// 类使用 Pascal命名法,且使用 名词。
function TextNode(options) {
}

// 函数名 使用 动宾短语。
function getStyle(element) {
}

// boolean 类型的变量使用 is 或 has 开头。
var isReady = false;
var hasMoreCommands = false;

// Promise对象 用 动宾短语的进行时 表达。
var loadingData = ajax.get('url');
loadingData.then(callback);

注释

单行注释必须独占一行。// 后跟一个空格。

避免使用 /*...*/ 这样的多行注释。有多行注释内容时,使用多个单行注释。

自文档化的文档说明 what,而不是 how。为了便于代码阅读和自文档化,以下内容必须包含以 /**...*/ 形式的块注释中。

  • 文件
  • namespace
  • 函数或方法
  • 类属性
  • 事件
  • 全局变量
  • 常量
  • AMD 模块

文件顶部必须包含文件注释,用 @file 标识文件说明。文件注释中可以用 @author 标识开发者信息。命名空间使用 @namespace 标识。类或构造函数使用 @class 标记。常量必须使用 @const 标记,并包含说明和类型信息。

1
2
3
4
5
/**
* @file Describe the file
* @author author-name(mail-name@domain.com)
* author-name2(mail-name2@domain.com)
*/

函数/方法注释必须包含函数说明,有参数和返回值时必须使用注释标识。参数和返回值注释必须包含类型信息和说明。对 Object 中各项的描述, 必须使用 @param 标识。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 函数描述
*
* @param {Object} option 参数描述
* @param {string} option.url option项描述,比较长
* 那就换行了.
* @param {string=} option.method option项描述,可选参数
* @return {string} 返回值描述
*/

function foo(option) {
return option.url + option.method;
}

必须使用 @event 标识事件,事件参数的标识与方法描述的参数标识相同。在会广播事件的函数前使用 @fires 标识广播的事件,在广播事件代码前使用 @event 标识事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 点击处理
*
* @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'
}
);
};

有时我们会使用一些特殊标记进行说明。特殊标记必须使用单行注释的形式。下面列举了一些常用标记:

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

语言特性

尽可能使用简洁的表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 字符串为空
if (!name) {
// ......
}

// 字符串非空
if (name) {
// ......
}

// 数组非空
if (collection.length) {
// ......
}

对于相同变量或表达式的多值条件,用 switch 代替 if。

1
2
3
4
5
6
7
8
9
10
11
12
// good
switch (typeof variable) {
case 'object':
// ......
break;
case 'number':
// ......
break;
case 'string':
// ......
break;
}

对有序集合进行顺序无关的遍历时,使用逆序遍历。(逆序遍历可以节省变量,代码比较优化)

1
2
3
4
5
var len = elements.length;
while (len--) {
var element = elements[len];
// ......
}

类型检测

类型检测优先使用 typeof。对象类型检测使用 instanceof。null 或 undefined 的检测使用 == null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 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'

类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 转换成 `string` 时,使用 + ''
num + '';

// 转换成 number 时,通常使用 +
+str;

// string 转换成 number,要转换的字符串结尾包含非数字并期望忽略时
// 使用 parseInt,并指定进制
parseInt('200px', 10);

// 转换成 boolean 时,使用 !!
var num = 3.14;
!!num;

// number 去除小数点,使用 Math.floor / Math.round / Math.ceil
var num = 3.14;
Math.ceil(num); // ceil向上取整,floor向下取整,round四舍五入

数组

遍历数组不使用 for in。不因为性能的原因自己实现数组排序功能,尽量使用数组的 sort 方法。清空数组使用 .length = 0

函数

一个函数的长度控制在 50 行以内。复杂的操作应进一步抽取,通过函数的调用来体现流程。一个函数的参数控制在 6 个以内。通过 options 参数传递非数据输入型参数。

如下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 移除某个元素
*
* @param {Node} element 需要移除的元素
* @param {Object} options 相关的逻辑配置
* @param {boolean} options.removeEventListeners 是否同时将所有注册在元素上的事件移除
*/

function removeElement(element, options) {
element.parent.removeChild(element);
if (options.removeEventListeners) {
element.clearEventListeners();
}
}

闭包

使用 IIFE 避免 Lift 效应。

在引用函数外部变量时,函数执行时外部变量的值由运行时决定而非定义时,最典型的场景如下:

1
2
3
4
5
6
7
8
9
10
11
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 效应 。解决的方式是通过额外加上一层闭包函数,将需要的外部变量作为参数传递来解除变量的绑定关系:

1
2
3
4
5
6
7
8
9
var tasks = [];
for (var i = 0; i < 5; i++) {
// 注意有一层额外的闭包
tasks[tasks.length] = (function (i) {
return function () {
console.log('Current cursor is at ' + i);
};
})(i);
}

面向对象

类的继承方案,实现时需要修正 constructor。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 构建类之间的继承关系
*
* @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;
}

属性在构造函数中声明,方法在原型中声明。原型对象的成员被所有实例共享,能节约内存占用。所以编码时我们应该遵守这样的原则:原型对象包含程序不会修改的成员,如方法函数或配置项。

避免修改外部传入的对象。可以使用额外的对象来维护,或者通过 deepClone 等手段将自身维护的对象与外部传入的分离,保证不会相互影响。

具备强类型的设计:

  • 如果一个属性被设计为 boolean 类型,则不要使用 1 / 0
    作为其值。对于标识性的属性,如对代码体积有严格要求,可以从一开始就设计为 number 类型且将 0 作为否定值。
  • 从 DOM 中取出的值通常为 string 类型,如果有对象或函数的接收类型为 number 类型,提前作好转换,而不是期望对象、函数可以处理多类型的值。

事件

自定义事件的 事件名 必须全小写。设计自定义事件时,应考虑禁止默认行为。常见禁止默认行为的方式有两种:

  • 事件监听函数中 return false。
  • 事件对象中包含禁止默认行为的方法,如 preventDefault。

浏览器环境

元素获取:遍历元素集合时,尽量缓存集合长度。如需多次操作同一集合,则应将集合转为数组。

样式获取:获取元素实际样式信息时,应使用 getComputedStylecurrentStyle。通过 style 只能获得内联定义或通过 JavaScript 直接设置的样式。通过 CSS class 设置的元素样式无法直接通过 style 获取。

样式设置:尽可能通过为元素添加预定义的 className 来改变元素样式,避免直接操作 style 设置。通过 style 对象设置元素样式时,对于带单位非 0 值的属性,不允许省略单位。

操作 DOM 时,尽量减少页面 reflow。页面 reflow 是非常耗时的行为,非常容易导致性能瓶颈。

使用 addEventListener 时第三个参数使用 false(保证和IE的一致性)。

参考

JavaScript 编码规范

坚持原创技术分享,您的支持将鼓励我继续创作!