JS开发规范
# JS开发规范
# 基本规则
[MUST] 遵循《命名规范》和《内容格式与编码规范》。
# 代码格式
[RECOMMENDED] 遵循 "Prettier 风格指南"。
解释:Prettier 是一个完全自动的 "风格指南"。可以让团队和项目拥有共同的代码格式。
# 引号
[MUST] 使用单引号''。在字符串中包含引号的情况下,选择转义次数最少的那一种。例如:使用"It's gettin' better!",而不是 'It\'s gettin\' better!'。
Eslint 对应规则:[quotes]
示例:
// 不推荐
const foo = "foo";
// 推荐
const foo = 'foo';
2
3
4
5
6
[MUST] 只对那些无效的标示使用引号
Eslint 对应规则:[quote-props]
解释:通常我们认为这种方式主观上更易读。不仅优化了代码高亮,而且也更容易被许多 JS 引擎优化。
示例:
// 不推荐
const obj = {
'foo': 3,
'bar': 4,
'data-blah': 5,
};
// 推荐
const obj = {
foo: 3,
bar: 4,
'data-blah': 5,
};
2
3
4
5
6
7
8
9
10
11
12
13
14
# 分号
[MUST] 每条语句的末尾必须使用分号。
Eslint 对应规则:[semi]
示例:
// 不推荐
const name = "ESLint"
// 推荐
const name = "ESLint";
2
3
4
5
6
# 命名
[MUST] 对于变量和函数名统一使用驼峰命名法。用小驼峰命名法来命名你的对象、函数、实例,用大驼峰命名法来命名类。
Eslint 对应规则:[camelcase]
解释:当命名变量时,风格指南一般会分为骆驼拼写法 (variableName) 和下划线拼写法 (variable_name) 两大阵营。该规则推荐使用骆驼拼写法的用法。
示例:
// 不推荐
const my_favorite_color = "#112C85";
// 推荐
const myFavoriteColor = "#112C85";
2
3
4
5
6
[MUST] 构造函数首字母应该大写。
Eslint 对应规则:[new-cap]
解释:构造函数也是一个常规函数,唯一区别是可以使用new来调用。首字母大写可以更容易的确定哪些函数被用作构造函数。
示例:
// 不推荐
new foo();
new foo.bar();
// 推荐
new Foo();
new foo.Bar();
Foo();
2
3
4
5
6
7
8
9
[RECOMMENDED] 获取当前执行环境的上下文时,统一使用that作为this的别名
Eslint 对应规则:[consistent-this]
解释:在整个项目中确保团队成员使用同样的别名是一个很有必要的事情。
示例:
// 不推荐
const self = this;
// 推荐
const that = this;
2
3
4
5
6
[MUST] 禁止将标识符定义为受限的名字。
Eslint 对应规则:[no-shadow-restricted-names]
解释:ES5 中全局对象的属性值 (NaN、Infinity、undefined)和严格模式下被限定的标识符 eval、arguments 在 JavaScript 中被认为是受限制的名称。将它们定义为其他含义可能会产生意想不到的结果,并使阅读代码的其他人感到困惑。
示例:
// 不推荐
const undefined = 1;
function foo(NaN) {}
function Infinity() {}
// 推荐
console.log(undefined);
console.log(NaN);
console.log(Infinity);
2
3
4
5
6
7
8
9
10
# 变量
[MUST] 推荐使用const或let而不是var来声明变量。
Eslint 对应规则:[no-var]
解释:ECMAScript 6 允许程序员使用let和const关键字在块级作用域而非函数作用域下声明变量。块级作用域能帮助程序员避免错误。
示例:
// 不推荐
var foo = 1;
// 推荐
let foo = 1;
const bar = 2;
2
3
4
5
6
7
[MUST] 对于声明后从未被重新赋值的变量,应该使用const声明。
Eslint 对应规则:[prefer-const]
解释:如果一个变量从未被重新赋值,使用const声明会更好。const声明告诉读者,"这个变量永远不会被重新赋值",减少认知负担,提高可维护性。
示例:
// 不推荐
let a = 3;
console.log(a);
// 推荐
const a = 3;
console.log(a);
2
3
4
5
6
7
8
[MUST] 禁止修改使用const关键字声明的变量。
Eslint 对应规则:[no-const-assign]
解释:它会引发一个运行时错误。如果你确实想修改这个变量,那么应该使用let声明。
示例:
// 不推荐
const foo = 1;
foo = 2;
// 推荐
let foo = 1;
foo = 2;
for (const bar in [1, 2, 3]) {
console.log(bar);
}
2
3
4
5
6
7
8
9
10
11
12
[MUST] 禁用未声明的变量,除非显式地在/*global ...*/注释中指定。
Eslint 对应规则:[no-undef]
示例:
// 不推荐
foo(bar);
// 推荐
function foo() {}
const bar = 1;
foo(bar);
if (typeof baz === 'number') {
}
2
3
4
5
6
7
8
9
10
11
[MUST] 禁止未使用过的变量。
Eslint 对应规则:[no-unused-vars]
解释:在代码中声明了变量,但没有在任何地方使用,这很可能是由于不完整的重构造成的错误。这样变量占用了代码中的空间,会导致读者的混淆。
示例:
// 不推荐
let foo = 1;
foo = 2;
function bar(baz) {}
const { baz, ...rest } = data;
// 推荐
let foo = 1;
console.log(foo);
function bar(baz) {}
bar();
const { baz, ...rest } = data;
console.log(baz, rest);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[MUST] 禁止在变量定义之前使用它们。
Eslint 对应规则:[no-use-before-define]
解释:在 ES6 标准之前的 JavaScript 中,某个作用域中变量和函数的声明会被提前到作用域顶部,所以可能存在这种情况:此变量在声明前被使用。这会扰乱读者,部分人认为最好的做法是使用变量之前先声明变量。在 ES6 中,块级绑定 (let和const) 引入 “temporal dead zone”,当企图使用未声明的变量会抛出ReferenceError。
示例:
// 不推荐
console.log(foo);
const foo = 1;
new Baz();
class Baz {}
// 推荐
const foo = 1;
console.log(foo);
bar();
function bar() {}
class Baz {}
new Baz();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[MUST] 禁止删除变量。
Eslint 对应规则:[no-delete-var]
解释:delete的目的是删除对象的属性。使用delete操作删除一个变量可能会导致意外情况发生。
[MUST] 不允许初始化变量值为undefined。
Eslint 对应规则:[no-undef-init]
解释:初始化变量值为undefined是多余的。
示例:
// 不推荐
let foo = undefined;
// 推荐
let foo;
2
3
4
5
6
[MUST] 禁止未使用过的表达式。
Eslint 对应规则:[no-unused-expressions]
解释:对程序状态没有影响的未使用表达式往往是个逻辑错误。
示例:
// 不推荐
1;
foo;
('foo');
foo && bar;
foo || bar;
foo ? bar : baz;
`bar`;
// 推荐
'use strict';
foo && bar();
foo || bar();
foo ? bar() : baz();
foo`bar`;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 标签
[MUST] 尽量避免使用标签语句。
Eslint 对应规则:[no-labels]
解释:虽然在某些情况下使用标签很方便,但不赞成将标签作为一种复杂流程控制的补救措施。
示例:
// 不推荐
label:
if (a) {
break label;
}
// 推荐
for (let i = 0; i < 5; i++) {
if (i === 1) {
continue;
}
console.log(i);
}
// 0 2 3 4
2
3
4
5
6
7
8
9
10
11
12
13
14
[MUST] 禁止使用同一作用域下的同名的变量做为标签。
Eslint 对应规则:[no-label-var]
示例:
// 不推荐
const x = foo;
function bar() {
x:
for (;;) {
break x;
}
}
// 推荐
function foo() {
const q = t;
}
function bar() {
q:
for(;;) {
break q;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[MUST] 禁止出现未使用过的标签。
Eslint 对应规则:[no-unused-labels]
解释:只声明却没有使用的标签可能是由于不完全的重构造成的错误。
# 字符串
[MUST] 禁止不规则的空白。
Eslint 对应规则:[no-irregular-whitespace]
解释:无效的或不规则的空白会导致 ECMAScript 5 解析问题,也会使代码难以调试(类似于混合 tab 和空格的情况)。
[RECOMMENDED] 不允许在字符类语法中出现由多个代码点组成的字符
Eslint 对应规则:[no-misleading-character-class]
解释:某些特殊字符很难看出差异,最好不要在正则中使用。
示例:
// 不推荐
/^[Á]$/u.test('Á'); // false
// 推荐
/^[A]$/u.test('A'); // true
2
3
4
5
6
[RECOMMENDED] 禁止在常规字符串中出现模板字面量占位符语法。
Eslint 对应规则:[no-template-curly-in-string]
解释:ECMAScript 6 允许程序员使用模板字面量创建包含变量或表达式的字符串,在两个反引号之间书写表达式比如`${variable}`,而不是使用字符串拼接。在使用模板字面量过程中很容易写错引号,写错成 "${variable}" 而不是在字符串中包含注入的表达式的值。
示例:
// 不推荐
const foo = 'Hello ${bar}';
// 推荐
const foo = 'Hello {bar}';
2
3
4
5
6
[MUST] 不要使用eval()。
Eslint 对应规则:[no-eval]
解释:JavaScript 中的eval()函数是有潜在危险的,而且经常被误用。在不可信的代码里使用eval()有可能使程序受到不同的注入攻击。eval()在大多数情况下可以被更好的解决问题的方法代替。
示例:
// 不推荐
eval('const foo = 0');
// 推荐
const foo = 0;
2
3
4
5
6
[MUST] 禁止使用字符串作为 setTimeout()、setInterval() 或 execScript() 的第一个参数。
Eslint 对应规则:[no-implied-eval]
解释:消除使用setTimeout()、setInterval()或execScript()时隐式的eval()。
示例:
// 不推荐
setTimeout('alert("Hello World");', 1000);
// 推荐
setTimeout(() => {
alert('Hello World');
}, 1000);
2
3
4
5
6
7
8
[MUST] 不要使用多行字符串。
Eslint 对应规则:[no-multi-str]
解释:它是 JavaScript 中的一个非正式的特性。
示例:
// 不推荐
const foo = 'Line 1\
Line 2';
// 推荐
const foo = `Line 1
Line 2`;
2
3
4
5
6
7
8
[MUST] 禁止不必要的转义。
Eslint 对应规则:[no-useless-escape]
解释:反斜线可读性差,因此仅当必要时才使用它。
示例:
// 不推荐
"\'";
'\"';
"\#";
"\e";
`\"`;
`\"${foo}\"`;
`\#{foo}`;
/\!/;
/\@/;
// 推荐
"\"";
'\'';
"\x12";
"\u00a9";
"xs\u2111";
`\``;
`\${${foo}}`;
`$\{${foo}}`;
/\\/g;
/\t/g;
/\w\$\*\^\./;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[RECOMMENDED] 当需要动态生成字符串时,应该使用模板字符串而不是字符串拼接。
Eslint 对应规则:[prefer-template]
解释:模板字符串更具可读性、多行语法更简洁以及更方便插入变量到字符串里。
示例:
// 不推荐
function sayHi(name) {
return 'How are you, ' + name + '?';
}
// 推荐
function sayHi(name) {
return `How are you, ${name}?`;
}
2
3
4
5
6
7
8
9
10
[MUST] 应该用大写字符而不是小写字符来定义转义序列值。
Eslint 对应规则:[unicorn/escape-case]
解释:使用大写字符而不是小写字符定义转义序列值。可以让转义值与标识符更容易区分,从而提高可读性。
示例:
// 不推荐
const foo = '\xa9';
// 推荐
const foo = '\xA9';
2
3
4
5
6
[RECOMMENDED] 使用codePointAt()和fromCodePoint()可以更好地支持Unicode。
Eslint 对应规则:[unicorn/prefer-code-point]
示例:
// 不推荐
const unicorn1 = '🦄'.charCodeAt(0).toString(16);
const unicorn2 = String.fromCharCode(0x1f984);
// 推荐
const unicorn1 = '🦄'.codePointAt(0).toString(16);
const unicorn2 = String.fromCodePoint(0x1f984);
2
3
4
5
6
7
8
[MUST] 优先使用字符串方法 startsWith / endsWith 替代其他复杂的写法。
Eslint 对应规则:[unicorn/prefer-string-starts-ends-with]
示例:
// 不推荐
const starts = /^bar/.test(baz);
const ends = /bar$/.test(baz);
// 推荐
const starts = baz.startsWith('bar');
const ends = baz.endsWith('bar');
2
3
4
5
6
7
8
# 对象
[MUST] 禁止在对象字面量中出现重复的键。
Eslint 对应规则:[no-dupe-keys]
解释:在你的应用程序中,如果对象字面量中出现多个属性有同样的键可能会到导致意想不到的情况出现。
示例:
// 不推荐
const foo = {
bar: 1,
bar: 2,
};
// 推荐
const foo = {
bar: 1,
baz: 2,
};
2
3
4
5
6
7
8
9
10
11
12
[MUST] 不允许使用Symbol对象的new操作。
Eslint 对应规则:[no-new-symbol]
解释:Symbol不和new操作符一起使用,而是作为函数调用。
示例:
// 不推荐
const foo = new Symbol('foo');
// 推荐
const foo = Symbol('foo');
2
3
4
5
6
[MUST] 禁止将Math、JSON和Reflect对象当作函数进行调用。
Eslint 对应规则:[no-obj-calls]
解释:如果你尝试像函数一样执行它们,将会抛出错误。
示例:
// 不推荐
const foo = Math();
const bar = JSON();
const baz = Reflect();
// 推荐
const foo = Math.random();
const bar = JSON.parse('{}');
const baz = Reflect.get({ x: 1, y: 2 }, 'x');
2
3
4
5
6
7
8
9
10
[MUST] 不要直接在对象实例上调用Object.prototype的内置方法,例如hasOwnProperty、propertyIsEnumerable、isPrototypeOf。
Eslint 对应规则:[no-prototype-builtins]
解释:对象可以具有属性,这些属性可以将Object.prototype的内建函数隐藏,可能导致意外行为或拒绝服务安全漏洞。例如:web 服务器解析来自客户机的 JSON 输入并直接在结果对象上调用hasOwnProperty是不安全的,因为恶意客户机可能发送一个JSON值,如{"hasOwnProperty": 1},并导致服务器崩溃。为了避免这种细微的 bug,最好总是从 Object.prototype调用这些方法。例如,foo.hasOwnProperty("bar")应该替换为Object.prototype.hasOwnProperty.call(foo, "bar")。
示例:
// 不推荐
const hasBarProperty = foo.hasOwnProperty('bar');
const isPrototypeOfBar = foo.isPrototypeOf(bar);
const barIsEnumerable = foo.propertyIsEnumerable('bar');
// 推荐
const hasBarProperty = Object.prototype.hasOwnProperty.call(foo, 'bar');
const isPrototypeOfBar = Object.prototype.isPrototypeOf.call(foo, bar);
const barIsEnumerable = {}.propertyIsEnumerable.call(foo, 'bar');
2
3
4
5
6
7
8
9
10
11
12
13
14
[MUST] 在不允许使用undefined值的情况下,不允许使用可选链。
Eslint 对应规则:[no-unsafe-optional-chaining]
解释:可选链式(?.)表达式可以用一个undefined的返回值来短路。因此,将已评估的可选链式表达式视为函数、对象、数字等,会导致TypeError或意外的结果。
示例:
// 不推荐
(obj?.foo)();
(obj?.foo ?? obj?.bar)();
// 推荐
obj?.foo();
(obj?.foo ?? bar)();
2
3
4
5
6
7
8
9
10
[MUST] 访问属性时优先使用点符号。只有当使用变量获取属性时才用方括号 []。
Eslint 对应规则:[dot-notation]
解释:在 JavaScript 中,你可以使用点号 (foo.bar) 或者方括号 (foo["bar"])来访问属性。然而,点号通常是首选,因为它更加易读,简洁,也更适于 JavaScript 压缩。
示例:
// 不推荐
const x = foo['bar'];
// 推荐
let baz = 'baz';
const x = foo.bar;
const y = foo[baz];
2
3
4
5
6
7
8
[MUST] 禁止扩展原生对象。
Eslint 对应规则:[no-extend-native]
解释:在 JavaScript 中,你可以扩展任何对象,包括内置或者"原生"对象。有时人们改变这些原生对象的行为,会影响到代码中的其它部分。
示例:
// 不推荐
Array.prototype.flat = function () {
// do something
};
[1, [2, 3]].flat();
// 推荐
function flat(arr) {
// do something
}
flat([1, [2, 3]]);
2
3
4
5
6
7
8
9
10
11
12
13
14
[MUST] 禁止对原生对象或只读的全局对象进行赋值。
Eslint 对应规则:[no-global-assign]
解释:JavaScript 环境包含很多内置的全局变量,比如浏览器环境的window和 Node.js 中的process。在几乎所有情况下,你都不希望给全局变量赋值,因为这样做可能会到导致无法访问到重要的功能。
示例:
// 不推荐
Object = null;
// 推荐
foo = null;
2
3
4
5
6
[MUST] 禁止使用对象的__iterator__属性。
Eslint 对应规则:[no-iterator]
解释:__iterator__属性曾是 SpiderMonkey 对 JavaScript 的扩展,被用来创建自定义迭代器,兼容JavaScript的for in和for each。然而,这个属性现在废弃了,所以不应再使用它。
示例:
// 不推荐
Foo.prototype.__iterator__ = function () {
return new FooIterator(this);
};
// 推荐
let foo = {};
foo[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
console.log([...foo]);
// [1, 2, 3]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[MUST] new创建对象实例后需要赋值给变量。
Eslint 对应规则:[no-new]
解释:对构造函数使用new的目的通常是创建一个特定类型的对象并且将该对象存储在变量中。
示例:
// 不推荐
new Foo();
// 推荐
const foo = new foo();
2
3
4
5
6
[MUST] 应该使用字面量创建对象,而不是使用Object构造函数。
Eslint 对应规则:[no-new-object]
解释:使用对象字面量节省字节、更简洁。
示例:
// 不推荐
const foo = new Object();
// 推荐
const foo = {};
2
3
4
5
6
[MUST] 禁止使用__proto__属性。
Eslint 对应规则:[no-proto]
解释:__proto__属性在 ECMAScript 3.1 中已经被弃用,并且不应该在代码中使用。使用 Object.getPrototypeOf 和 Object.setPrototypeOf 代替。
示例:
// 不推荐
const foo = bar.__proto__;
bar.__proto__ = baz;
// 推荐
const foo = Object.getPrototypeOf(bar);
Object.setPrototypeOf(bar, baz);
2
3
4
5
6
7
8
[MUST] 禁止在对象中使用不必要的计算属性。
Eslint 对应规则:[no-useless-computed-key]
示例:
// 不推荐
const foo = {
['1']: 1,
['bar']: 'bar',
};
// 推荐
const foo = {
1: 1,
bar: 'bar',
};
2
3
4
5
6
7
8
9
10
11
12
[MUST] 对象字面量中方法和属性应该尽量使用简写语法。
Eslint 对应规则:[object-shorthand]
解释:ECMAScript 6 提供了简写的形式去定义对象中的方法和属性。这个语法可以更清洁地定义复杂对象字面量。
示例:
// 不推荐
const foo = {
a: a, // 对象的属性
b: function () {}, // 对象的方法
};
// 推荐
const foo = {
a, // 对象的属性
b() {}, // 对象的方法
};
2
3
4
5
6
7
8
9
10
11
12
[MUST] 对象浅拷贝时,推荐使用扩展运算符(即...运算符),而不是Object.assign。获取对象指定的几个属性时,用对象的 rest 解构运算符(即...运算符)更好。
Eslint 对应规则:[prefer-object-spread]
解释:在ES2018中引入的对象扩展语法是一种声明式的替代方法,它比动态的、命令式的Object.assign表现更好。
示例:
// 不推荐
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
// 推荐
const original = { a: 1, b: 2 };
// 浅拷贝
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
// rest 解构运算符
const { a, ...noA } = copy; // noA => { b: 2, c: 3 }
2
3
4
5
6
7
8
9
10
11
[RECOMMENDED] 创建symbol时应该带有描述。
Eslint 对应规则:[symbol-description]
解释:使用 描述 更容易促进调试。
示例:
// 不推荐
const foo = Symbol();
// 推荐
const foo = Symbol('foo');
2
3
4
5
6
# 数组
[MUST] 数组的方法除了forEach之外,回调函数必须有返回值。
Eslint 对应规则:[array-callback-return]
解释:数组有几种方法用于过滤、映射和折叠。如果忘记在这些回调中写返回语句,那可能是个错误。
示例:
// 不推荐
const foo = [1, 2, 3].map((num) => {
console.log(num * num);
});
// 推荐
const foo = [1, 2, 3].map((num) => {
return num * num;
});
2
3
4
5
6
7
8
9
10
[RECOMMENDED] 避免使用稀疏数组,除非你确定它们在你的代码中很有用。
Eslint 对应规则:[no-sparse-arrays]
示例:
// 不推荐
const foo = [1, 2, , 3];
// 推荐
const foo = [1, 2, 3];
2
3
4
5
6
[MUST] 用字面量创建数组。避免使用Array构造函数。
Eslint 对应规则:[no-array-constructor]
解释:由于单参数的陷阱,和全局范围的Array可能被重定义,通常不允许使用Array的构造函数来创建数组。唯一的例外是通过给构造函数传入指定的一个数值来创建稀疏数组。
示例:
// 不推荐
const foo = Array(0, 1, 2); // [0, 1, 2]
const bar = new Array(0, 1, 2); // [0, 1, 2]
// 推荐
const foo = [0, 1, 2];
Array(3); // [empty × 3]
new Array(3); // [empty × 3]
Array(3).fill('foo'); // ["foo", "foo", "foo"]
new Array(3).fill('foo'); // ["foo", "foo", "foo"]
2
3
4
5
6
7
8
9
10
11
[MUST] 使用Array.isArray()代替instanceof Array。
Eslint 对应规则:[unicorn/no-instanceof-array]
解释:使用 instanceof 检查数组实例在不同的领域/上下文中不起作用。例如,浏览器中的frames/windows 或Node.js的vm模块。
示例:
// 不推荐
array instanceof Array;
[1, 2, 3] instanceof Array;
// 推荐
Array.isArray(array);
Array.isArray([1, 2, 3]);
2
3
4
5
6
7
8
[MUST] 当检查是否存在时,优先使用includes,而不是使用indexOf。
Eslint 对应规则:[unicorn/prefer-includes]
示例:
// 不推荐
[].indexOf('foo') !== -1;
x.indexOf('foo') != -1;
str.indexOf('foo') > -1;
'foobar'.indexOf('foo') >= 0;
x.indexOf('foo') === -1;
const isFound = foo.some((x) => x === 'foo');
const isFound2 = foo.some((x) => 'foo' === x);
const isFound3 = foo.some((x) => {
return x === 'foo';
});
// 推荐
[].includes('foo');
x.includes('foo');
str.includes('foo');
'foobar'.includes('foo');
!x.includes('foo');
const isFound = foo.includes('foo');
const isFound2 = foo.includes('foo');
const isFound3 = foo.includes('foo');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[RECOMMENDED] 用扩展运算符(即...)做数组浅拷贝。
示例:
// 不推荐
const items = [ 1, 2, 3 ];
const itemsCopy = [];
for (let i = 0; i < items.length; i ++) {
itemsCopy[i] = items[i];
}
// 推荐
const items = [ 1, 2, 3 ];
const itemsCopy = [...items];
2
3
4
5
6
7
8
9
10
11
12
[RECOMMENDED] 在无需 Mapping 的情况下,用...运算符代替Array.from来将一个可迭代的对象转换成数组。
解释:
Array.from()静态方法从一个类似数组或可迭代对象创建一个新的、浅层复制的Array实例。 它的语法第二个参数接收一个 Mapping 函数。可在转换的同时进行 Mapping 操作。
将一个类数组对象转换成数组:
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };
const arr = Array.from(arrLike);
2
将一个可迭代对象转换成数组时,使用...运算符要比使用Array.from()更加方便:
const foo = document.querySelectorAll('.foo');
const nodes = [...foo]; // 比 `const nodes = Array.from(foo);` 更简洁。
2
但如果同时还需进行 map 转换,则应该使用带有第二个参数的Array.from(),因为这样可以避免创建一个临时数组:
// 不推荐
const baz = [...foo].map(bar);
// 推荐
const baz = Array.from(foo, bar);
2
3
4
5
# 函数
[MUST] 禁止对function声明重新赋值。
Eslint 对应规则:[no-func-assign]
解释:虽然 JavaScript 解释器可以容忍对函数声明进行覆盖或重新赋值,但通常这是个错误或会导致问题出现。
示例:
// 不推荐
function foo() {}
foo = 1;
// 推荐
let foo = function () {};
foo = 1;
2
3
4
5
6
7
8
[RECOMMENDED] 函数使用默认参数时,非默认参数应该放在默认参数的前面。
Eslint 对应规则:[default-param-last]
解释:把默认参数放在最后,允许函数调用省略可选的尾部参数。
示例:
// 不推荐
function f(a = 0, b) {}
// 推荐
function f(a, b = 0) {}
2
3
4
[RECOMMENDED] 函数名应该与赋值给它们的变量名或属性名相匹配。
Eslint 对应规则:[func-name-matching]
示例:
// 不推荐
const foo = function bar() {};
// 推荐
const foo = function () {};
const bar = function bar() {};
2
3
4
5
6
7
[RECOMMENDED] 应该使用命名的function表达式。
Eslint 对应规则:[func-names]
解释:给函数表达式加个名字可以方便调试。
示例:
// 不推荐
const bar = function () {};
// 推荐
const bar = function bar() {};
2
3
4
5
6
[MUST] 避免使用arguments.caller或arguments.callee。
Eslint 对应规则:[no-caller]
解释:arguments.caller和arguments.callee的使用使一些代码优化变得不可能。在 JavaScript 的新版本中它们已被弃用,同时在 ECMAScript 5 的严格模式下,它们也是被禁用的。
示例:
// 不推荐
function foo(n) {
if (n <= 0) {
return;
}
arguments.callee(n - 1);
}
// 推荐
function foo(n) {
if (n <= 0) {
return;
}
foo(n - 1);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[MUST] 禁止不必要的函数绑定。
Eslint 对应规则:[no-extra-bind]
解释:bind()会增加性能损耗。
示例:
// 不推荐
(function () {
foo();
}.bind(bar));
// 推荐
(function () {
this.foo();
}.bind(bar));
2
3
4
5
6
7
8
9
10
[MUST] 禁止使用Function构造器创建函数。
Eslint 对应规则:[no-new-func]
解释:以这种方式创建函数将类似于字符串eval(),存在漏洞。
示例:
// 不推荐
const foo = new Function('a', 'b', 'return a + b');
// 推荐
const foo = function (a, b) {
return a + b;
};
2
3
4
5
6
7
8
[MUST] 不允许对函数参数进行重新赋值。
Eslint 对应规则:[no-param-reassign]
解释:对作为函数参数声明的变量进行赋值可能会产生误导并导致混乱的行为。
示例:
// 不推荐
function foo(bar) {
bar = bar || '';
}
// 推荐
function foo(bar_) {
bar = bar_ || '';
}
2
3
4
5
6
7
8
9
10
[MUST] 避免不必要的.call()和 .apply()。
Eslint 对应规则:[no-useless-call]
解释:因为Function.prototype.call()和Function.prototype.apply()比正常的函数调用效率低。
示例:
// 不推荐
foo.call(null, 1, 2, 3); // foo(1, 2, 3)
foo.apply(null, [1, 2, 3]); // foo(1, 2, 3)
foo.bar.call(foo, 1, 2, 3); // foo.bar(1, 2, 3);
foo.bar.apply(foo, [1, 2, 3]); // foo.bar(1, 2, 3);
// 推荐
foo.call(bar, 1, 2, 3);
foo.apply(bar, [1, 2, 3]);
foo.bar.call(baz, 1, 2, 3);
foo.bar.apply(baz, [1, 2, 3]);
2
3
4
5
6
7
8
9
10
11
12
13
14
[RECOMMENDED] 回调函数应该使用箭头函数
Eslint 对应规则:[prefer-arrow-callback]
解释:箭头函数中的this与定义该函数的上下文中的this一致,这通常才是你想要的。而且箭头函数是更简洁的语法。
示例:
// 不推荐
[1, 2, 3].map(function (x) {
const y = x + 1;
return x * y;
});
// 推荐
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
2
3
4
5
6
7
8
9
10
11
12
[MUST] 使用剩余参数语法(...)代替arguments
Eslint 对应规则:[prefer-rest-params]
解释:...可以明确你想用哪个参数。而且剩余参数是真数组,而arguments是类数组。
示例:
// 不推荐
function foo() {
console.log(arguments);
}
// 推荐
function foo(...args) {
console.log(args);
}
2
3
4
5
6
7
8
9
10
[RECOMMENDED] 使用扩展语法(...)调用多参数的函数。
Eslint 对应规则:[prefer-spread]
解释:在 ES2015之前,只能使用 Function.prototype.apply()来调用多参数函数。现在可以使用扩展语法(...)来调用多参数函数。
示例:
// 不推荐
const x = [1, 2, 3, 4, 5];
console.log.apply(console, x);
// 推荐
const x = [1, 2, 3, 4, 5];
console.log(...x);
2
3
4
5
6
7
8
[MUST] generator函数内必须有yield
Eslint 对应规则:[require-yield]
示例:
// 不推荐
function* foo() {
return 1;
}
// 推荐
function* foo() {
yield 1;
return 2;
}
2
3
4
5
6
7
8
9
10
11
[MUST] 用默认参数语法而不是在函数里对参数重新赋值。
示例:
// 不推荐
function handleThings(opts) {
opts = opts || {};
}
// 推荐
function handleThings(opts = {}) {
// ...
}
2
3
4
5
6
7
8
9
10
# 类与构造函数
[MUST] 使用class语法。避免直接操作prototype。
解释:class 语法更简洁更易理解。
示例:
// 不推荐
function foo() {
// ...
}
foo.prototype.bar = function () {
// ...
};
// 推荐
class foo {
constructor() {
// ...
}
bar() {
// ...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[MUST] 用extends实现继承。
解释:它是一种内置的方法来继承原型功能而不破坏instanceof。
示例:
class foo {
// ...
}
class bar extends foo {
// ...
}
2
3
4
5
6
7
[RECOMMENDED] 方法可以返回this来实现链式调用。
示例:
class Jedi {
jump() {
this.jumping = true;
return this;
}
setHeight(height) {
this.height = height;
return this;
}
}
const luke = new Jedi();
luke.jump().setHeight(20);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[MUST] 派生类的构造函数必须调用super()。非派生类的构造器不得调用super()。
Eslint 对应规则:[constructor-super]
解释:如果没有遵守这一点,JavaScript引擎将引发一个运行时错误。
示例:
// 不推荐
class Foo extends Bar {
constructor() {}
}
// 推荐
class Foo extends Bar {
constructor() {
super();
}
}
2
3
4
5
6
7
8
9
10
11
12
[MUST] 禁止修改类声明的变量。
Eslint 对应规则:[no-class-assign]
解释:大多数情况下,这种修改是一个错误。
示例:
// 不推荐
class Foo {}
Foo = {};
// 推荐
class Foo {}
2
3
4
5
6
7
[MUST] 不允许从构造函数中返回值。
Eslint 对应规则:[no-constructor-return]
解释:在JavaScript中,在类的构造函数中返回一个值可能是一个错误。
示例:
// 不推荐
class Foo {
constructor(bar) {
this.bar = bar;
return bar;
}
}
// 推荐
class Foo {
constructor(bar) {
if (!bar) {
return;
}
this.bar = bar;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[MUST] 不允许类成员中有重复的名称。
Eslint 对应规则:[no-dupe-class-members]
解释:如果类成员中有同名的声明,最后一个声明将会覆盖其它声明。它可能导致意外的行为。
示例:
// 不推荐
class Foo {
bar() {}
bar() {}
}
// 推荐
class Foo {
bar() {}
baz() {}
}
2
3
4
5
6
7
8
9
10
11
12
[MUST] 禁止在构造函数中,在调用super()之前使用this或super。
Eslint 对应规则:[no-this-before-super]
解释:在派生类的构造函数中,如果在调用super()之前使用this或super,它将会引发一个引用错误。
示例:
// 不推荐
class Foo extends Bar {
constructor() {
this.foo = 1;
super();
}
}
// 推荐
class Foo extends Bar {
constructor() {
super();
this.foo = 1;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[MUST] 禁止不必要的构造函数。
Eslint 对应规则:[no-useless-constructor]
解释:ES2015 为没有指定构造函数的类提供了默认构造函数。因此,没有必要提供一个空的构造函数或只是简单的调用父类这样的构造函数。
示例:
// 不推荐
class Foo {
constructor() {}
}
class Bar extends Foo {
constructor(...args) {
super(...args);
}
}
// 推荐
class Foo {
constructor() {
doSomething();
}
}
class Bar extends Foo {
constructor(...args) {
super(...args);
doSomething();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 模块
[RECOMMENDED] 浏览器环境使用ES2015的模块系统,使用import/export导入导出模块。而不是非标准的模块系统。
示例:
import { es6 } from './foo';
export default es6;
2
3
[RECOMMENDED] 不要使⽤通配符的⽅式进⾏模块导⼊。
解释:这可以有效的保证你在导出模块时,会有⼀个 默认导出(export default)
示例:
// 不推荐
import * as request from './request';
// 推荐
import request from './request';
2
3
4
5
6
[RECOMMENDED] 不要直接从import中直接export。
解释:应该明确的使用import和export来保证代码的一致性。
示例:
// 不推荐
export { request as default } from './request';
// 推荐
import request from './request';
export default request;
2
3
4
5
6
7
8
[RECOMMENDED] 禁止重复模块导入。
Eslint 对应规则:[no-duplicate-imports]
解释:为每个模块使用单一的import语句会使代码更加简洁,因为你会看到从该模块导入的所有内容都在同一行。
示例:
// 不推荐
import { readFile } from 'fs';
import { writeFile } from 'fs';
// 推荐
import { readFile, writeFile } from 'fs';
2
3
4
5
6
7
[MUST] 不允许对导入的绑定进行赋值。
Eslint 对应规则:[no-import-assign]
解释:ES模块对导入的绑定的更新会导致运行时错误。
示例:
// 不推荐
import foo from 'foo';
foo = 1;
import * as bar from 'bar';
bar.baz = 1;
// 推荐
import foo from 'foo';
foo.baz = 1;
import * as bar from 'bar';
bar.baz.qux = 1;
2
3
4
5
6
7
8
9
10
11
12
13
14
[MUST] 禁止在import和export和解构赋值时将引用重命名为相同的名字。
Eslint 对应规则:[no-useless-rename]
解释:这种操作完全冗余。
示例:
// 不推荐
import { foo as foo } from 'foo';
const bar = 1;
export { bar as bar };
let { baz: baz } = foo;
// 推荐
import { foo } from 'foo';
const bar = 1;
export { bar };
let { baz } = foo;
2
3
4
5
6
7
8
9
10
11
12
[MUST] 禁止使用绝对路径导入模块。
Eslint 对应规则:[import/no-absolute-path]
解释:Node.js允许使用绝对路径导入模块,如/home/xyz/file.js。这是一种不好的做法,因为它将使用它的代码与你的计算机联系在一起,因此使得它在npm上分发的包中无法使用。
示例:
// 不推荐
import a from '/foo';
const b = require('/foo');
// 推荐
import a from 'foo';
import b from './foo';
const c = require('./foo');
2
3
4
5
6
7
8
9
10
[MUST] 禁止import使用 Webpack loader 语法加载程序。
Eslint 对应规则:[import/no-webpack-loader-syntax]
解释:一旦使用 Webpack 语法,就意味着把代码耦合到了模块绑定器。这种语法是非标准的,最好是在 Webpack 的配置文件里写好 loader 配置。
示例:
// 不推荐
import myModule from 'my-loader!my-module';
import theme from 'style!css!./theme.css';
// 推荐
import myModule from 'my-module';
import theme from './theme.css';
2
3
4
5
6
7
8
[RECOMMENDED] 不要导出可变的东西。
Eslint 对应规则:[import/no-mutable-exports]
解释:变化通常都是需要避免,特别是当你要输出可变的绑定。虽然在某些场景下可能需要这种技术,但总的来说应该导出常量。
示例:
// 不推荐
let foo = 3;
export { foo };
// 推荐
const foo = 3;
export { foo };
2
3
4
5
6
7
8
[MUST] import导入语句应该出现在其他语句之前。
Eslint 对应规则:[import/first]
解释:因为import会被提升到代码最前面运行,因此将他们放在最前面以防止发生意外行为。
示例:
// 不推荐
initWith(foo);
import bar from './bar'; // <- reported
// 推荐
import bar from './bar';
// some module-level initializer
initWith(foo); // <- reported
2
3
4
5
6
7
8
9
10
11
[RECOMMENDED] import JavaScript 文件不用包含扩展名
Eslint 对应规则:[import/extensions]
解释:使用扩展名对于重构不友好。
示例:
// 不推荐
import foo from './foo.js';
// 推荐
import foo from './foo';
2
3
4
5
6
[RECOMMENDED] 在一个单一导出模块里,用export default更好。
Eslint 对应规则:[import/prefer-default-export]
解释:鼓励使用更多文件,每个文件只导出一次,这样可读性和可维护性更好。
示例:
// 不推荐
export function foo() {}
// 推荐
export default function foo() {}
2
3
4
5
6
# 比较运算符与相等
[MUST] 禁止与-0进行比较。
Eslint 对应规则:[no-compare-neg-zero]
解释:试图与-0进行比较的代码并不会达到预期。也就是说像x === -0的代码对于+0和-0都有效。作者可能想要用Object.is(x, -0)。
示例:
// 不推荐
if (foo === -0) {
}
// 推荐
if (foo === 0) {
}
2
3
4
5
6
7
8
[MUST] 不允许使用在运行时会失去精度的数字字面量。
Eslint 对应规则:[no-loss-of-precision]
解释:在JS中,数字被存储为双精度浮点数字。因此,只能保持一定范围内的精度。如果输入超出范围的数字,这些数字将在转换为Number类型的过程中会丢失精度,并会导致意外的行为。
示例:
// 不推荐
const foo = 5123000000000000000000000000001;
// 推荐
const foo = 12345;
2
3
4
5
6
[MUST] 禁止自身赋值。
Eslint 对应规则:[no-self-assign]
解释:自身赋值不起任何作用,可能是由于不完整的重构造成的错误。也表明你的工作还没做完。
示例:
// 不推荐
let foo=1;
let obj={a:1};
foo = foo;
obj.a = obj.a;
// 推荐
foo = bar;
2
3
4
5
6
7
8
[MUST] 禁止自身比较。
Eslint 对应规则:[no-self-compare]
解释:变量与其自身进行比较通常来说是一个错误,要么是打字错误要么是重构错误。它都会给读者造成困扰并且可能会引入运行错误。唯一情况是当你测试变量是否为NaN的时候,你可将一个变量与它自己进行比较。然而,在这种情况下,使用typeof x === 'number' && isNaN(x)或Number.isNaN函数更为合适。
示例:
// 不推荐
if (foo === foo) {
}
if (NaN === NaN) {
}
// 推荐
if (foo === bar) {
}
if (isNaN(foo)) {
}
2
3
4
5
6
7
8
9
10
11
12
[MUST] 禁止对关系运算符 in、instanceof的左操作数使用!操作符。
Eslint 对应规则:[no-unsafe-negation]
解释:开发者可能会错误地输入 !key in object,而他们几乎肯定是指 !(key in object) 来测试一个键不在一个对象中。!obj instanceof Ctor也是如此。
示例:
// 不推荐
if (!key in object) {
}
if (!obj instanceof SomeClass) {
}
// 推荐
if (!(key in object)) {
}
if (!(obj instanceof SomeClass)) {
}
2
3
4
5
6
7
8
9
10
11
12
[MUST] 禁止与NaN的比较。要求调用 isNaN()检查NaN。
Eslint 对应规则:[use-isnan]
解释:因为在 JavaScript 中 NaN 独特之处在于它不等于任何值,包括它本身,与 NaN 进行比较的结果是令人困惑的:NaN !== NaN 或 NaN != NaN 的结果都为 true 。因此,应该使用 Number.isNaN() 或 全局的 isNaN() 函数来测试一个值是否是 NaN。
示例:
// 不推荐
if (foo === NaN) {
}
// 推荐
if (isNaN(foo)) {
}
2
3
4
5
6
7
8
[MUST] typeof表达式必须和有效的字符串进行比较。
Eslint 对应规则:[valid-typeof]
解释:对于绝大多数用例而言,typeof操作符的结果是以下字符串字面量中的一个:"undefined"、"object"、"boolean"、"number"、"string"、"function"、"symbol"和"bigint"。把typeof操作符的结果与其它字符串进行比较,通常是书写错误。
示例:
// 不推荐
if (typeof foo === 'numbe') {
}
// 推荐
if (typeof foo === 'number') {
}
2
3
4
5
6
7
8
[MUST] 使用类型安全的===和!==操作符代替==和!=操作符。例外:obj == null可以用来检查null || undefined。
Eslint 对应规则:[eqeqeq]
解释:这样做的原因是==和!=会遵循“抽象等价比较算法”作强制类型转换。
示例:
// 不推荐
if (foo == 1) {
}
// 推荐
if (foo === 1) {
}
if (bar !== null) {
}
2
3
4
5
6
7
8
9
10
# 流程与控制语句
[MUST] 禁止在if、for、while和do...while语句中出现模棱两可的赋值操作符。
Eslint 对应规则:[no-cond-assign]
解释:在条件语句中使用赋值操作符是有效的。然而,很难判断某个特定的赋值是否是有意为之。因为在条件语句中,很容易将一个比较运算符(像==)错写成赋值运算符(如=)。
示例:
// 不推荐
if (foo = 0) {
}
// 推荐
if (foo === 0) {
}
if (bar === (foo = 0)) {
}
2
3
4
5
6
7
8
9
10
[MUST] 禁止在if语句和?:三元表达式语句的条件中使用常量表达式。
Eslint 对应规则:[no-constant-condition]
解释:将一个常量表达式(比如,一个字面值)作为一个测试条件可能是个书写错误或者为了触发某个特定的行为。
示例:
// 不推荐
if (true) {
}
const foo = 0 ? 'bar' : 'baz';
// 推荐
for (; true; ) {
if (foo === 0) break;
}
while (true) {
if (foo === 0) break;
}
2
3
4
5
6
7
8
9
10
11
12
13
[MUST] 不允许在if-else-if链中出现重复条件。
Eslint 对应规则:[no-dupe-else-if]
解释:在同一链中的两个相同的测试条件几乎总是代码中的一个错误。除非表达式中存在副作用,否则重复的表达式将评估为与链中早期的相同表达式相同的真或假值,这意味着其分支永远无法执行。
示例:
// 不推荐
if (foo) {
console.log(foo);
} else if (foo) {
console.log(bar);
}
// 推荐
if (foo) {
console.log(foo);
} else if (bar) {
console.log(bar);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
[MUST] 禁止在同一个switch语句中出现重复的case标签。
Eslint 对应规则:[no-duplicate-case]
解释:如果一个switch语句中的case子句中出现重复的测试表达式,那么很有可能是某个程序员拷贝了一个case子句但忘记了修改测试表达式。
示例:
// 不推荐
switch (foo) {
case 1:
break;
case 2:
break;
case 1:
break;
}
// 推荐
switch (foo) {
case 1:
break;
case 2:
break;
case 3:
break;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[RECOMMENDED] 禁止case语句落空。
Eslint 对应规则:[no-fallthrough]
解释:
在 JavaScript 中,switch 语句是一种比较容易出错的结构,在某种程度上这要归功于 case 的落空能力。比如:
switch (foo) {
case 1:
doSomething();
case 2:
doSomethingElse();
}
2
3
4
5
6
7
在这个例子中,如果 foo 值为 1,两个 case 语句都会执行。你可以使用 break 阻止这种情况,例如以下例子:
switch (foo) {
case 1:
doSomething();
break;
case 2:
doSomethingElse();
}
2
3
4
5
6
7
8
当你不想要落空时是没有问题的,但是,如果落空是有意为之呢,没有办法来表明这一点。使用匹配 /falls?\s?through/i 的正则表达式的注释来表明落空是有意为之的,被认为是一个最佳实践。
switch (foo) {
case 1:
doSomething();
// falls through
case 2:
doSomethingElse();
}
switch (foo) {
case 1:
doSomething();
// fall through
case 2:
doSomethingElse();
}
switch (foo) {
case 1:
doSomething();
// fallsthrough
case 2:
doSomethingElse();
}
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
在这个例子中,不会再引起困惑。很明显,第一个 case 的意思是要落空到第二个 case。
[MUST] 禁止在return、throw、continue和break语句后出现不可达代码。
Eslint 对应规则:[no-unreachable]
解释:因为return、throw、continue和break语句无条件地退出代码块,其之后的任何语句都不会被执行。不可达语句通常是个错误。
示例:
// 不推荐
function foo() {
return;
const bar = 1;
}
// 推荐
function foo() {
return;
// const bar = 1;
}
2
3
4
5
6
7
8
9
10
11
12
[RECOMMENDED] 禁止在finally语句块中出现控制流语句。
Eslint 对应规则:[no-unsafe-finally]
解释:JavaScript 暂停try和catch语句块中的控制流语句,直到finally语句块执行完毕。所以,当return、throw、break和continue出现在finally中时, try和catch语句块中的控制流语句将被覆盖,这被认为是意外的行为。
示例:
// 不推荐
function foo() {
try {
return 1;
} finally {
// finally 会在 try 之前执行,故会 return 2
return 2;
}
}
// 推荐
function foo() {
try {
return 1;
} finally {
console.log(2);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[MUST] 强制将switch语句中的默认子句放在最后。
Eslint 对应规则:[default-case-last]
解释:如果一个switch语句有一个缺省子句,那么把它定义为最后一个子句被认为是一种最佳做法。
示例:
// 不推荐
switch (foo) {
default:
bar();
break;
case 1:
baz();
break;
}
// 推荐
switch (foo) {
case 1:
baz();
break;
default:
bar();
break;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[MUST] 禁止词法声明 (let、const、function和class) 出现在switch语句的case或default子句中。
Eslint 对应规则:[no-case-declarations]
解释:词法声明在整个switch语句块中是可见的,但是它只有在运行到它定义的case语句时,才会进行初始化操作。为了保证词法声明语句只在当前case语句中有效,应将你的子句包裹在块中。
示例:
// 不推荐
switch (foo) {
case 1:
const x = 1;
break;
}
// 推荐
switch (foo) {
case 1: {
const x = 1;
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[MUST] 禁止if语句中return语句之后有else块。
Eslint 对应规则:[no-else-return]
解释:如果if块中包含了一个return语句,else块就成了多余的了。可以将其内容移至块外。
[MUST] 禁止出现空语句块。
Eslint 对应规则:[no-empty]
解释:空语句块,如果不是技术上的错误,通常是由于不完整的重构造成的。这会造成代码阅读上的困惑。
示例:
// 不推荐
if (foo) {
}
// 推荐
if (foo) {
// do something
}
try {
// do something
} catch (e) {}
2
3
4
5
6
7
8
9
10
11
12
[MUST] 禁用不必要的嵌套块。
Eslint 对应规则:[no-lone-blocks]
示例:
// 不推荐
{
foo();
}
// 推荐
if (foo) {
bar();
}
2
3
4
5
6
7
8
9
10
[MUST] 禁止if语句作为唯一语句出现在else语句块中。
Eslint 对应规则:[no-lonely-if]
解释:如果if语句作为唯一的语句出现在else语句块中,往往使用else if形式会使代码更清晰。
[MUST] 禁止在return语句中使用赋值语句,除非使用括号把它们括起来。
Eslint 对应规则:[no-return-assign]
解释:避免“比较”和“赋值”的误操作。
示例:
// 不推荐
function foo() {
return bar = 1;
}
function fo() {
return (bar = 1);
}
// 推荐
function foo() {
bar = 1;
return bar;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[MUST] 禁止多余的 return 语句。
Eslint 对应规则:[no-useless-return]
# 循环与迭代
[MUST] for循环中更新子句的计数器必须朝着正确的方向移动。
Eslint 对应规则:[for-direction]
解释:如果一个for循环的停止条件永远无法到达,比如,计数器在错误的方向上移动,将陷入无限循环。当存在这样的无限循环时,惯例是改用while循环。更典型的是,无限循环是个 bug。
示例:
// 不推荐
for (let i = 0; i < 10; i--) {
// do something
}
// 推荐
for (let i = 0; i < 10; i++) {
// do something
}
2
3
4
5
6
7
8
9
10
[RECOMMENDED] 禁用一成不变的循环条件。
Eslint 对应规则:[no-unmodified-loop-condition]
解释:循环条件中的变量在循环中是要经常改变的。如果不是这样,那么可能是个错误。
示例:
// 不推荐
let foo = 10;
while (foo) {
console.log(foo);
}
// 推荐
let foo = 10;
while (foo) {
console.log(foo);
foo--;
}
2
3
4
5
6
7
8
9
10
11
12
13
[MUST] 禁止出现只允许一次迭代的主体的循环。
Eslint 对应规则:[no-unreachable-loop]
解释:一个永远无法达到第二次迭代的循环是代码中可能存在的错误。在极少数情况下,只有一个迭代(或最多一个迭代)是预期的行为,代码应该被重构为使用if条件,而不是while、do-while和for循环。在这种情况下,避免使用循环结构被认为是一种最佳做法。
示例:
// 不推荐
for (foo of bar) {
if (foo.id === id) {
doSomething(foo);
}
break;
}
// 推荐
for (foo of bar) {
if (foo.id === id) {
doSomething(foo);
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[RECOMMENDED] 使用for in循环时,应该使用if语句对结果进行筛选。
Eslint 对应规则:[guard-for-in]
解释:在使用for in遍历对象时,会把从原型链继承来的属性也包括进来。这样会导致意想不到的项出现。
示例:
// 不推荐
for (key in foo) {
doSomething(key);
}
// 推荐
for (key in foo) {
if (Object.prototype.hasOwnProperty.call(foo, key)) {
doSomething(key);
}
}
2
3
4
5
6
7
8
9
10
11
12
[RECOMMENDED] 使用for…of替代Array#forEach(…)方法。
Eslint 对应规则:[unicorn/no-array-for-each]
解释:与forEach方法相比,for...of语句更快,可读性更好,且能够通过break或return提前退出。
示例:
// 不推荐
array.forEach((element) => {
bar(element);
});
array.forEach((element, index) => {
bar(element, index);
});
array.forEach((element, index, array) => {
bar(element, index, array);
});
// 推荐
for (const element of array) {
bar(element);
}
for (const [index, element] of array.entries()) {
bar(element, index);
}
for (const [index, element] of array.entries()) {
bar(element, index, array);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[RECOMMENDED] 优先使用 JavaScript 高级函数代替for-in、for-of。
解释:高级函数更简洁,且处理返回值的纯函数比处理副作用更容易。用数组的这些迭代方法: map() / every() / filter() / find() / findIndex() / reduce() / some() / ... , 用对象的这些方法 Object.keys() / Object.values() / Object.entries() 去产生一个数组,这样你就能去遍历对象了。
示例:
// 不推荐
const numbers = [1, 2, 3, 4, 5];
let sum = 0;
for (let num of numbers) {
sum += num;
}
// 推荐
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((total, num) => total + num, 0);
2
3
4
5
6
7
8
9
10
11
# 正则表达式
[MUST] 禁止在正则表达式中出现空字符集。
Eslint 对应规则:[no-empty-character-class]
解释:在正则表达式中空字符集不能匹配任何字符,它们可能是打字错误。
示例:
// 不推荐
const reg = /abc[]/;
// 推荐
const reg = /abc[a-z]/;
2
3
4
5
6
[MUST] 禁止在RegExp构造函数中出现无效的正则表达式。
Eslint 对应规则:[no-invalid-regexp]
解释:在正则表达式字面量中无效的模式在代码解析时会引起SyntaxError,但是RegExp的构造函数中无效的字符串只在代码执行时才会抛出SyntaxError。
示例:
// 不推荐
const reg1 = new RegExp('[');
const reg2 = new RegExp('.', 'z');
// 推荐
const reg1 = new RegExp('[a-z]');
const reg2 = new RegExp('.', 'g');
2
3
4
5
6
7
8
[MUST] 不允许在正则表达式中出现无用的反向引用。
Eslint 对应规则:[no-useless-backreference]
解释:某些反向引用语法上没问题,但是会永远匹配到空字符串。
示例:
// 不推荐
/^(?:(a)|\1b)$/; // reference to (a) into another alternative
// 推荐
/^(?:(a)|(b)\2)$/; // reference to (b)
2
3
4
5
6
[MUST] 禁止正则表达式字面量中出现多个空格。
Eslint 对应规则:[no-regex-spaces]
解释:正则表达式可以很复杂和难以理解,这就是为什么要保持它们尽可能的简单,以避免出现错误。在使用正则表达式想要匹配多个空格时,最好是只使用一个空格,然后指定需要多少个。
示例:
// 不推荐
const reg1 = /foo bar/;
const reg2 = new RegExp('foo bar');
// 推荐
const reg1 = /foo {3}bar/;
const reg2 = new RegExp('foo {3}bar');
2
3
4
5
6
7
8
[MUST] 优先使用正则表达式字面量,而不是RegExp构造函数
Eslint 对应规则:[prefer-regex-literals]
示例:
// 不推荐
new RegExp('abc');
new RegExp('\\.', 'g');
// 推荐
/abc/;
/\./g;
new RegExp(prefix + 'abc');
2
3
4
5
6
7
8
9
# 解构
[MUST] 禁止使用空解构模式。
Eslint 对应规则:[no-empty-pattern]
解释: 当使用解构赋值时,可能创建了一个不起作用的模式。把空的花括号放在嵌入的对象的解构模式右边时,就会产生这种情况,例如:
// 不会创建任何变量
const {
a: {},
} = foo;
2
3
4
在以上代码中,没有创建新的变量,因为 a 只是一个辅助位置,而 {} 将包含创建的变量,例如:
// 创建变量 b
const {
a: { b },
} = foo;
2
3
4
在许多情况下,作者本来打算使用一个默认值,却错写成空对象,例如:
// 创建变量 a
const { a = {} } = foo;
2
这两种模式直接的区别是微妙的,因为空模式看起来像是一个对象字面量。
[RECOMMENDED] 使用解构赋值语法从数组索引或对象属性中创建变量。而不是通过成员表达式。
Eslint 对应规则:[prefer-destructuring]
示例:
// 不推荐
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
// 推荐
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
2
3
4
5
6
7
8
9
10
11
12
13
[MUST] 多个返回值用对象的解构,而不是数组解构。
解释:你可以在后期添加新的属性或者变换变量的顺序而不会破坏原有的引用。
示例:
// 不推荐
function processInput(input) {
return [left, right, top, bottom];
}
// 调用者需要想一想返回值的顺序
const [left, __, top] = processInput(input);
// 推荐
function processInput(input) {
return { left, right, top, bottom };
}
// 调用者只需要选择他想用的值就好了
const { left, top } = processInput(input);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 类型转换
[MUST] 禁止不必要的布尔类型转换。
Eslint 对应规则:[no-extra-boolean-cast]
示例:
// 不推荐
if (!!foo) {
}
if (Boolean(foo)) {
}
// 推荐
if (foo) {
}
if (!foo) {
}
2
3
4
5
6
7
8
9
10
11
12
[MUST] 禁止对String,Number和Boolean使用new操作符。
Eslint 对应规则:[no-new-wrappers]
解释:使用new会创建原始包装对象的实例,这意味着typeof将返回 "object",而不是 "string"、"number"或 "boolean"。另外,对于布尔型对象。每个对象都是 truthy,这意味着Boolean的一个实例总是被解析为true,即使它的实际值是false。由于这些原因,避免与new一起使用原始包装类型被认为是一种最佳做法。
示例:
// 不推荐
const s = new String('foo');
const n = new Number(1);
const b = new Boolean(true);
// 推荐
const s = String(someValue);
const n = Number(someValue);
const b = Boolean(someValue);
2
3
4
5
6
7
8
9
10
# 错误处理
[MUST] 禁止对catch子句中的异常参数重新赋值。
Eslint 对应规则:[no-ex-assign]
解释:在try语句中的catch子句中,如果意外地(或故意地)给异常参数赋值,是不可能引用那个位置的错误的。由于没有arguments对象提供额外的方式访问这个异常,对它进行赋值绝对是毁灭性的。
示例:
// 不推荐
try {
} catch (e) {
e = 10;
}
// 推荐
try {
} catch (e) {
console.error(e);
}
2
3
4
5
6
7
8
9
10
11
12
[MUST] 用throw抛错时,应该抛出Error对象而不是字符串。
Eslint 对应规则:[no-throw-literal]
示例:
// 不推荐
throw 'foo';
// 推荐
throw new Error('foo');
2
3
4
5
6
[MUST] 禁止不必要的catch子句
Eslint 对应规则:[no-useless-catch]
解释:只重新抛出原始错误的catch子句是冗余的,对程序的运行时行为没有影响。这些冗余子句可能会导致混乱和代码膨胀,所以最好不要使用这些不必要的catch子句。
示例:
// 不推荐
try {
doSomethingThatMightThrow();
} catch (e) {
throw e;
}
// 推荐
doSomethingThatMightThrow();
try {
doSomethingThatMightThrow();
} catch (e) {
doSomethingBeforeRethrow();
throw e;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[MUST] 在创建内置错误时必须传递一个消息值。
Eslint 对应规则:[unicorn/error-message]
解释:在创建内置错误对象的实例时传递一个消息值,可以让代码的可读性和可调试性更好。
示例:
// 不推荐
throw Error();
throw Error('');
throw new TypeError();
const error = new AggregateError(errors);
// 推荐
throw Error('Unexpected property.');
throw new TypeError('Array expected.');
const error = new AggregateError(errors, 'Promises rejected.');
2
3
4
5
6
7
8
9
10
11
[MUST] 抛出错误时使用new。
Eslint 对应规则:[unicorn/throw-new-error]
解释:虽然可以在不使用new关键字的情况下创建新错误,但最好是显式的。
示例:
// 不推荐
throw Error();
throw TypeError('unicorn');
throw lib.TypeError();
// 推荐
throw new Error();
throw new TypeError('unicorn');
throw new lib.TypeError();
2
3
4
5
6
7
8
9
10
# Promise
[MUST] 禁止使用异步函数作为 Promise executor。
Eslint 对应规则:[no-async-promise-executor]
解释:
new Promise 构造函数接收一个 executor 函数作为参数,该函数具有 resolve 和 reject 两个参数,可用于控制创建的 Promise 的状态。例如:
const result = new Promise(function executor(resolve, reject) {
readFile('foo.txt', function (err, result) {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
2
3
4
5
6
7
8
9
executor 函数也可以是 async function。然而,这通常是一个错误,原因如下:
- 如果异步
executor函数抛出一个错误,这个错误将会丢失,并且不会导致新构造的Promise被拒绝。这可能使会调试和处理一些错误变得困难。 - 如果一个
Promise executor函数使用了await,这通常表示实际上没有必要使用new Promise构造函数,或者可以减少new Promise构造函数的范围。
示例:
// 不推荐
new Promise(async (resolve) => {
setTimeout(resolve, 1000);
});
// 推荐
new Promise((resolve) => {
setTimeout(resolve, 1000);
});
2
3
4
5
6
7
8
9
10
[MUST] 不允许从Promise执行器函数中返回值。
Eslint 对应规则:[no-promise-executor-return]
解释:执行器函数通常启动一些异步操作。一旦完成,执行器应该用结果调用resolve,如果发生错误则reject。执行器的返回值会被忽略。从执行器函数中返回一个值是一个可能的错误,因为返回的值不能被使用,它不会以任何方式影响承诺 。
示例:
// 不推荐
new Promise((resolve, reject) => {
if (someCondition) {
return defaultResult;
}
getSomething((err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
// 推荐
new Promise((resolve, reject) => {
if (someCondition) {
resolve(defaultResult);
return;
}
getSomething((err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
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
[MUST] Promise的reject中必须传入Error对象,而不是字面量
Eslint 对应规则:[prefer-promise-reject-errors]
解释:在Promise中只传递内置的Error对象实例给reject()函数作为自定义错误,被认为是个很好的实践。Error对象会自动存储堆栈跟踪,在调试时,通过它可以用来确定错误是从哪里来的。如果Promise使用了非Error的值作为拒绝原因,那么就很难确定reject在哪里产生。
示例:
// 不推荐
Promise.reject('foo');
new Promise((resolve, reject) => {
reject();
});
new Promise((resolve, reject) => {
reject('foo');
});
// 推荐
Promise.reject(new Error('foo'));
new Promise((resolve, reject) => {
reject(new Error('foo'));
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[RECOMMENDED] 避免嵌套then()或catch()语句。
Eslint 对应规则:[promise/no-nesting]
示例:
// 不推荐
myPromise.then((val) => doSomething(val).then(doSomethingElse));
myPromise.then((val) => doSomething(val).catch(errors));
myPromise.catch((err) => doSomething(err).then(doSomethingElse));
myPromise.catch((err) => doSomething(err).catch(errors));
// 推荐
myPromise.then(doSomething).then(doSomethingElse).catch(errors);
2
3
4
5
6
7
8
9
10
11
12
# Get-Set 访问器
[MUST] getter函数中必须存在return语句。
Eslint 对应规则:[getter-return]
解释:get语法将对象属性绑定到一个函数,该函数在查找该属性时将被调用。每个getter都期望有返回值。
示例:
// 不推荐
const user = {
get name() {
// do something
},
};
class User {
get name() {
return;
}
}
// 推荐
const user = {
get name() {
return 'Alex';
},
};
class User {
get name() {
return this.name;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[MUST] 不允许从setter返回值。
Eslint 对应规则:[no-setter-return]
解释:虽然从setter返回一个值不会产生错误,但返回的值被忽略了。因此,从setter返回一个值要么是不必要的,要么是一个可能的错误,因为返回的值不能被使用。
示例:
// 不推荐
const foo = {
set bar(value) {
this.barValue = 'bar ' + value;
return this.barValue;
},
};
// 推荐
const foo = {
set bar(value) {
this.barValue = 'bar ' + value;
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[MUST] 每个属性,如果定义了setter,也必须定义相应的getter。
Eslint 对应规则:[accessor-pairs]
解释:没有getter,你不能读取这个属性,该属性也就不会被用到。
示例:
// 不推荐
const foo = {
set bar(value) {
this.barValue = 'bar ' + value;
},
};
// 推荐
const foo = {
set bar(value) {
this.barValue = 'bar ' + value;
},
get bar() {
return this.barValue;
},
};
const bar = {
get foo() {
return this.fooValue;
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# DOM
[MUST] 对DOM节点应使用.textContent替换.innerText。
Eslint 对应规则:[unicorn/prefer-dom-node-text-content]
解释:.textContent的性能和兼容性更好。
示例:
// 不推荐
const text = foo.innerText;
foo.innerText = '🦄';
// 推荐
const text = foo.textContent;
foo.textContent = '🦄';
2
3
4
5
6
7
8
# Node
[MUST] 在Node.js中使用回调模式时,应该处理错误。
Eslint 对应规则:[n/handle-callback-err]
解释:在Node.js中,处理异步行为的一个常见模式被称为回调模式。这种模式期望一个错误对象或null作为回调的第一个参数。忘记处理这些错误会导致你的应用程序中出现一些非常奇怪的行为。
示例:
// 不推荐
function loadData(err, data) {
doSomething();
}
// 推荐
function loadData(err, data) {
if (err) {
}
doSomething();
}
2
3
4
5
6
7
8
9
10
11
12
[MUST] 当一个函数被命名为cb或callback时,那么它的第一个参数必须是undefined、null、一个Error类、或一个Error的子类。
Eslint 对应规则:[n/no-callback-literal]
解释:当调用一个使用Node.js错误优先回调模式的回调函数时,你所有的错误都应该使用Error类或它的子类。如果没有错误,使用undefined或null也是可以接受的。
示例:
// 不推荐
cb('this is an error string');
cb({ a: 1 });
callback(0);
// 推荐
cb(undefined);
cb(null, 5);
callback(new Error('some error'));
callback(someVariable);
2
3
4
5
6
7
8
9
10
11
[MUST] 不要使用exports = {},而应该使用module.exports = exports = {}。
Eslint 对应规则:[n/no-exports-assign]
解释:避免冲突。
示例:
// 不推荐
exports = {
foo: 1,
};
// 推荐
module.exports.foo = 1;
2
3
4
5
6
7
8
[MUST] 不允许使用new操作符调用require函数。
Eslint 对应规则:[n/no-new-require]
解释: require 函数用于包含存在于独立文件中的模块,例如:
const appHeader = require('app-header');
一些模块返回一个构造函数,这有可能导致诸如以下的代码:
const appHeader = new require('app-header');
不幸的是,这引入了一个很高的混淆可能性,因为代码作者很可能是想写:
const appHeader = new (require('app-header'))();
出于这个原因,通常最好是不允许这种特殊的表达方式。
[MUST] 不允许用__dirname和__filename连接字符串。
Eslint 对应规则:[n/no-path-concat]
解释:
在 Node.js 中,__dirname和__filename全局变量分别包含了当前执行的脚本文件的目录路径和文件路径。有时,开发者试图使用这些变量来创建其他文件的路径,例如:
const fullPath = __dirname + '/foo.js';
然而,这其中有几个问题。首先,你不能确定脚本是在什么类型的系统上运行。Node.js 可以在任何计算机上运行,包括 Windows,它使用不同的路径分隔符。因此,使用字符串连接和假设 Unix 风格的分隔符,很容易创建一个无效的路径。也有可能出现双重分隔符,或者以其他方式导致无效的路径。
为了避免在如何创建正确的路径方面出现任何混乱,Node.js 提供了 path 模块。这个模块使用系统特定的信息,总是返回正确的值。因此,你可以把前面的例子改写成:
const fullPath = path.join(__dirname, 'foo.js');
这个例子不需要包括分隔符,因为path.join()会以最合适的方式完成。另外,你可以使用path.resolve()来检索完全限定的路径:
const fullPath = path.resolve(__dirname, 'foo.js');
path.join()和 path.resolve()都是在创建文件或目录路径时替代字符串连接的合适方法。
[MUST] 禁止使用废弃的API。
Eslint 对应规则:[n/no-deprecated-api]
解释:Node有许多废弃的API。社区将在未来从Node中删除这些API,所以我们不应该使用这些。
示例:
// 不推荐
const fs = require('fs');
fs.exists('./foo.js', function () {});
// 推荐
const fs = require('fs');
fs.stat('./foo.js', function () {});
2
3
4
5
6
7
8
[MUST] 使用Buffer.from()和Buffer.alloc(),而不是已废弃的new Buffer()。
Eslint 对应规则:[unicorn/no-new-buffer]
解释:后者自Node.js 4以来已经被废弃。
示例:
// 不推荐
const buffer1 = new Buffer('7468697320697320612074c3a97374', 'hex');
const buffer2 = new Buffer([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
const buffer3 = new Buffer(10);
// 推荐
const buffer1 = Buffer.from('7468697320697320612074c3a97374', 'hex');
const buffer2 = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
const buffer3 = Buffer.alloc(10);
2
3
4
5
6
7
8
9
10
# 其它
[MUST] 禁止在生产环境的代码中使用debugger语句。
Eslint 对应规则:[no-debugger]
解释:debugger语句用于告诉 JavaScript 执行环境停止执行并在代码的当前位置启动调试器。随着现代调试和开发工具的出现,使用debugger已不是最佳实践。产品代码不应该包含debugger。
[RECOMMENDED] 不要再生产环境代码中使用alert、prompt和confirm。
Eslint 对应规则:[no-alert]
解释:JavaScript 的alert、confirm和prompt被广泛认为是突兀的 UI 元素,应该被一个更合适的自定义的 UI 界面代替。此外, alert经常被用于调试代码,部署到生产环境之前应该删除。
[MUST] 禁止使用逗号操作符。
Eslint 对应规则:[no-sequences]
解释:逗号操作符包含多个表达式,其中只有一个是可使用的。它从左到右计算每一个操作数并且返回最后一个操作数的值。这往往掩盖了它的副作用,它的使用经常会发生事故。
示例:
// 不推荐
foo = doSomething(), 1;
// 推荐
doSomething();
foo = 1;
2
3
4
5
6
7
[MUST] 禁用with语句。
Eslint 对应规则:[no-with]
解释:with是潜在的问题,因为它会在当前的域中增加对象成员,使得区分实际块中的变量指的是什么变的不可能。