一种写JavaScript更合理的代码风格。
其他代码风格指南
- 1.1 原始值: 当你访问一个原始类型的时候,你可以直接使用它的值。
stringnumberbooleannullundefinedsymbol```javascript const foo = 1; let bar = foo;
bar = 9;
console.log(foo, bar); // => 1, 9
- 标识符不能完全被支持,因此在针对不支持的浏览器或者环境时不应该使用它们。- [1.2](#types--complex) **复杂类型**: 当你访问一个复杂类型的时候,你需要一个值得引用。- `object`- `array`- `function````javascriptconst foo = [1, 2];const bar = foo;bar[0] = 9;console.log(foo[0], bar[0]); // => 9, 9
[
](#table-of-contents)
引用
- 2.1 使用
const定义你的所有引用;避免使用var。 eslint:[prefer-const](https://eslint.org/docs/rules/prefer-const.html),[no-const-assign](https://eslint.org/docs/rules/no-const-assign.html)为什么? 这样能够确保你不能重新赋值你的引用,否则可能导致错误或者产生难以理解的代码。.
// badvar a = 1;var b = 2;// goodconst a = 1;const b = 2;
- 2.2 如果你必须重新赋值你的引用, 使用
let代替var。 eslint:[no-var](https://eslint.org/docs/rules/no-var.html)为什么?
let是块级作用域,而不像var是函数作用域.
// badvar count = 1;if (true) {count += 1;}// good, use the let.let count = 1;if (true) {count += 1;}
- 2.3 注意,let 和 const 都是块级范围的。
[// const 和 let 只存在于他们定义的块中。{let a = 1;const b = 1;}console.log(a); // ReferenceErrorconsole.log(b); // ReferenceError
](#table-of-contents)
对象
- 3.1 使用字面语法来创建对象。 eslint:
[no-new-object](https://eslint.org/docs/rules/no-new-object.html)```javascript // bad const item = new Object();
// good const item = {};
- [3.2](#es6-computed-properties) 在创建具有动态属性名称的对象时使用计算属性名。> 为什么? 它允许你在一个地方定义对象的所有属性。```javascriptfunction getKey(k) {return `a key named ${k}`;}// badconst obj = {id: 5,name: 'San Francisco',};obj[getKey('enabled')] = true;// goodconst obj = {id: 5,name: 'San Francisco',[getKey('enabled')]: true,};
3.3 使用对象方法的缩写。 eslint:
[object-shorthand](https://eslint.org/docs/rules/object-shorthand.html)```javascript // bad const atom = { value: 1,addValue: function (value) { return atom.value + value; }, };
// good const atom = { value: 1,
addValue(value) { return atom.value + value; }, };
- [3.4](#es6-object-concise) 使用属性值的缩写。 eslint: `[object-shorthand](https://eslint.org/docs/rules/object-shorthand.html)`> 为什么? 它的写法和描述较短。```javascriptconst lukeSkywalker = 'Luke Skywalker';// badconst obj = {lukeSkywalker: lukeSkywalker,};// goodconst obj = {lukeSkywalker,};
- 3.5 在对象声明的时候将简写的属性进行分组。
为什么? 这样更容易的判断哪些属性使用的简写。
const anakinSkywalker = 'Anakin Skywalker';const lukeSkywalker = 'Luke Skywalker';// badconst obj = {episodeOne: 1,twoJediWalkIntoACantina: 2,lukeSkywalker,episodeThree: 3,mayTheFourth: 4,anakinSkywalker,};// goodconst obj = {lukeSkywalker,anakinSkywalker,episodeOne: 1,twoJediWalkIntoACantina: 2,episodeThree: 3,mayTheFourth: 4,};
- 3.6 只使用引号标注无效标识符的属性。 eslint:
[quote-props](https://eslint.org/docs/rules/quote-props.html)为什么? 总的来说,我们认为这样更容易阅读。 它提升了语法高亮显示,并且更容易通过许多 JS 引擎优化。
// badconst bad = {'foo': 3,'bar': 4,'data-blah': 5,};// goodconst good = {foo: 3,bar: 4,'data-blah': 5,};
- 3.7 不能直接调用
Object.prototype的方法,如:hasOwnProperty、propertyIsEnumerable和isPrototypeOf。为什么? 这些方法可能被以下问题对象的属性追踪 - 相应的有
{ hasOwnProperty: false }- 或者,对象是一个空对象 (Object.create(null))。
// badconsole.log(object.hasOwnProperty(key));// goodconsole.log(Object.prototype.hasOwnProperty.call(object, key));// bestconst has = Object.prototype.hasOwnProperty; // 在模块范围内的缓存中查找一次/* or */import has from 'has'; // https://www.npmjs.com/package/has// ...console.log(has.call(object, key));
- 3.8 更喜欢对象扩展操作符,而不是用
[Object.assign](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)浅拷贝一个对象。 使用对象的 rest 操作符来获得一个具有某些属性的新对象。``javascript // very bad const original = { a: 1, b: 2 }; const copy = Object.assign(original, { c: 3 }); // 变异的original` ಠ_ಠ delete copy.a; // 这….
// bad const original = { a: 1, b: 2 }; const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
// good const original = { a: 1, b: 2 }; const copy = { …original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
const { a, …noA } = copy; // noA => { b: 2, c: 3 }
[](#table-of-contents)<a name="5c4cf69a"></a>## 数组- [4.1](#arrays--literals) 使用字面语法创建数组。 eslint: `[no-array-constructor](https://eslint.org/docs/rules/no-array-constructor.html)````javascript// badconst items = new Array();// goodconst items = [];
- 4.2 使用 Array#push 取代直接赋值来给数组添加项。 ```javascript const someStack = [];
// bad someStack[someStack.length] = ‘abracadabra’;
// good someStack.push(‘abracadabra’);
- [4.3](#es6-array-spreads) 使用数组展开方法 `...` 来拷贝数组。```javascript// badconst len = items.length;const itemsCopy = [];let i;for (i = 0; i < len; i += 1) {itemsCopy[i] = items[i];}// goodconst itemsCopy = [...items];
- 4.4 将一个类数组对象转换成一个数组, 使用展开方法
...代替[Array.from](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/from)。 ```javascript const foo = document.querySelectorAll(‘.foo’);
// good const nodes = Array.from(foo);
// best const nodes = […foo];
- [4.5](#arrays--mapping) 对于对迭代器的映射,使用 [Array.from](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/from) 替代展开方法 `...` , 因为它避免了创建中间数组。```javascript// badconst baz = [...foo].map(bar);// goodconst baz = Array.from(foo, bar);
- 4.6 在数组回调方法中使用 return 语句。 如果函数体由一个返回无副作用的表达式的单个语句组成,那么可以省略返回值, 具体查看 8.2。 eslint:
[array-callback-return](https://eslint.org/docs/rules/array-callback-return)```javascript // good [1, 2, 3].map((x) => { const y = x + 1; return x * y; });
// good [1, 2, 3].map(x => x + 1);
// bad - 没有返回值,意味着在第一次迭代后 acc 没有被定义
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
const flatten = acc.concat(item);
acc[index] = flatten;
});
// good [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => { const flatten = acc.concat(item); acc[index] = flatten; return flatten; });
// bad inbox.filter((msg) => { const { subject, author } = msg; if (subject === ‘Mockingbird’) { return author === ‘Harper Lee’; } else { return false; } });
// good inbox.filter((msg) => { const { subject, author } = msg; if (subject === ‘Mockingbird’) { return author === ‘Harper Lee’; }
return false; });
- [4.7](#arrays--bracket-newline) 如果数组有多行,则在开始的时候换行,然后在结束的时候换行。```javascript// badconst arr = [[0, 1], [2, 3], [4, 5],];const objectInArray = [{id: 1,}, {id: 2,}];const numberInArray = [1, 2,];// goodconst arr = [[0, 1], [2, 3], [4, 5]];const objectInArray = [{id: 1,},{id: 2,},];const numberInArray = [1,2,];
[
](#table-of-contents)
解构
- 5.1 在访问和使用对象的多个属性的时候使用对象的解构。 eslint:
[prefer-destructuring](https://eslint.org/docs/rules/prefer-destructuring)为什么? 解构可以避免为这些属性创建临时引用。
// badfunction getFullName(user) {const firstName = user.firstName;const lastName = user.lastName;return `${firstName} ${lastName}`;}// goodfunction getFullName(user) {const { firstName, lastName } = user;return `${firstName} ${lastName}`;}// bestfunction getFullName({ firstName, lastName }) {return `${firstName} ${lastName}`;}
- 5.2 使用数组解构。 eslint:
[prefer-destructuring](https://eslint.org/docs/rules/prefer-destructuring)```javascript const arr = [1, 2, 3, 4];
// bad const first = arr[0]; const second = arr[1];
// good const [first, second] = arr;
- [5.3](#destructuring--object-over-array) 对于多个返回值使用对象解构,而不是数组解构。> 为什么? 你可以随时添加新的属性或者改变属性的顺序,而不用修改调用方。```javascript// badfunction processInput(input) {// 处理代码...return [left, right, top, bottom];}// 调用者需要考虑返回数据的顺序。const [left, __, top] = processInput(input);// goodfunction processInput(input) {// 处理代码...return { left, right, top, bottom };}// 调用者只选择他们需要的数据。const { left, top } = processInput(input);
[
](#table-of-contents)
字符
- 6.1 使用单引号
''定义字符串。 eslint:[quotes](https://eslint.org/docs/rules/quotes.html)```javascript // bad const name = “Capt. Janeway”;
// bad - 模板文字应该包含插值或换行。
const name = Capt. Janeway;
// good const name = ‘Capt. Janeway’;
- [6.2](#strings--line-length) 使行超过100个字符的字符串不应使用字符串连接跨多行写入。> 为什么? 断开的字符串更加难以工作,并且使代码搜索更加困难。```javascript// badconst errorMessage = 'This is a super long error that was thrown because \of Batman. When you stop to think about how Batman had anything to do \with this, you would get nowhere \fast.';// badconst errorMessage = 'This is a super long error that was thrown because ' +'of Batman. When you stop to think about how Batman had anything to do ' +'with this, you would get nowhere fast.';// goodconst errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';
- 6.3 当以编程模式构建字符串时,使用字符串模板代替字符串拼接。 eslint:
[prefer-template](https://eslint.org/docs/rules/prefer-template.html)[template-curly-spacing](https://eslint.org/docs/rules/template-curly-spacing)为什么? 字符串模板为您提供了一种可读的、简洁的语法,具有正确的换行和字符串插值特性。
// badfunction sayHi(name) {return 'How are you, ' + name + '?';}// badfunction sayHi(name) {return ['How are you, ', name, '?'].join();}// badfunction sayHi(name) {return `How are you, ${ name }?`;}// goodfunction sayHi(name) {return `How are you, ${name}?`;}
- 6.4 不要在字符串上使用
eval(),它打开了太多漏洞。 eslint:[no-eval](https://eslint.org/docs/rules/no-eval)
- 6.5 不要转义字符串中不必要的字符。 eslint:
[no-useless-escape](https://eslint.org/docs/rules/no-useless-escape)为什么? 反斜杠损害了可读性,因此只有在必要的时候才会出现。
// badconst foo = '\'this\' \i\s \"quoted\"';// goodconst foo = '\'this\' is "quoted"';const foo = `my name is '${name}'`;
方法
- 7.1 使用命名的函数表达式代替函数声明。 eslint:
[func-style](https://eslint.org/docs/rules/func-style)为什么? 函数声明是挂起的,这意味着在它在文件中定义之前,很容易引用函数。这会损害可读性和可维护性。如果您发现函数的定义是大的或复杂的,以至于它干扰了对文件的其余部分的理解,那么也许是时候将它提取到它自己的模块中了!不要忘记显式地命名这个表达式,不管它的名称是否从包含变量(在现代浏览器中经常是这样,或者在使用诸如Babel之类的编译器时)。这消除了对错误的调用堆栈的任何假设。 (Discussion)
// badfunction foo() {// ...}// badconst foo = function () {// ...};// good// 从变量引用调用中区分的词汇名称const short = function longUniqueMoreDescriptiveLexicalFoo() {// ...};
- 7.2 Wrap立即调用函数表达式。 eslint:
[wrap-iife](https://eslint.org/docs/rules/wrap-iife.html)为什么? 立即调用的函数表达式是单个单元 - 包装, 并且拥有括号调用, 在括号内, 清晰的表达式。 请注意,在一个到处都是模块的世界中,您几乎不需要一个 IIFE 。
// immediately-invoked function expression (IIFE) 立即调用的函数表达式(function () {console.log('Welcome to the Internet. Please follow me.');}());
- 7.3 切记不要在非功能块中声明函数 (
if,while, 等)。 将函数赋值给变量。 浏览器允许你这样做,但是他们都有不同的解释,这是个坏消息。 eslint:[no-loop-func](https://eslint.org/docs/rules/no-loop-func.html)
- 7.4 注意: ECMA-262 将
block定义为语句列表。 函数声明不是语句。 ```javascript // bad if (currentUser) { function test() { console.log(‘Nope.’); } }
// good let test; if (currentUser) { test = () => { console.log(‘Yup.’); }; }
- [7.5](#functions--arguments-shadow) 永远不要定义一个参数为 `arguments`。 这将会优先于每个函数给定范围的 `arguments` 对象。```javascript// badfunction foo(name, options, arguments) {// ...}// goodfunction foo(name, options, args) {// ...}
- 7.6 不要使用
arguments, 选择使用 rest 语法...代替。 eslint:[prefer-rest-params](https://eslint.org/docs/rules/prefer-rest-params)为什么?
...明确了你想要拉取什么参数。 更甚, rest 参数是一个真正的数组,而不仅仅是类数组的arguments。
// badfunction concatenateAll() {const args = Array.prototype.slice.call(arguments);return args.join('');}// goodfunction concatenateAll(...args) {return args.join('');}
- 7.7 使用默认的参数语法,而不是改变函数参数。 ```javascript // really bad function handleThings(opts) { // No! We shouldn’t mutate function arguments. // Double bad: if opts is falsy it’ll be set to an object which may // be what you want but it can introduce subtle bugs. opts = opts || {}; // … }
// still bad function handleThings(opts) { if (opts === void 0) { opts = {}; } // … }
// good function handleThings(opts = {}) { // … }
- [7.8](#functions--default-side-effects) 避免使用默认参数的副作用。> 为什么? 他们很容易混淆。```javascriptvar b = 1;// badfunction count(a = b++) {console.log(a);}count(); // 1count(); // 2count(3); // 3count(); // 3
- 7.9 总是把默认参数放在最后。 ```javascript // bad function handleThings(opts = {}, name) { // … }
// good function handleThings(name, opts = {}) { // … }
- [7.10](#functions--constructor) 永远不要使用函数构造器来创建一个新函数。 eslint: `[no-new-func](https://eslint.org/docs/rules/no-new-func)`> 为什么? 以这种方式创建一个函数将对一个类似于 `eval()` 的字符串进行计算,这将打开漏洞。```javascript// badvar add = new Function('a', 'b', 'return a + b');// still badvar subtract = Function('a', 'b', 'return a - b');
- 7.11 函数签名中的间距。 eslint:
[space-before-function-paren](https://eslint.org/docs/rules/space-before-function-paren)[space-before-blocks](https://eslint.org/docs/rules/space-before-blocks)为什么? 一致性很好,在删除或添加名称时不需要添加或删除空格。
// badconst f = function(){};const g = function (){};const h = function() {};// goodconst x = function () {};const y = function a() {};
- 7.12 没用变异参数。 eslint:
[no-param-reassign](https://eslint.org/docs/rules/no-param-reassign.html)为什么? 将传入的对象作为参数进行操作可能会在原始调用程序中造成不必要的变量副作用。
// badfunction f1(obj) {obj.key = 1;}// goodfunction f2(obj) {const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;}
- 7.13 不要再赋值参数。 eslint:
[no-param-reassign](https://eslint.org/docs/rules/no-param-reassign.html)为什么? 重新赋值参数会导致意外的行为,尤其是在访问
arguments对象的时候。 它还可能导致性能优化问题,尤其是在 V8 中。
// badfunction f1(a) {a = 1;// ...}function f2(a) {if (!a) { a = 1; }// ...}// goodfunction f3(a) {const b = a || 1;// ...}function f4(a = 1) {// ...}
- 7.14 优先使用扩展运算符
...来调用可变参数函数。 eslint:[prefer-spread](https://eslint.org/docs/rules/prefer-spread)为什么? 它更加干净,你不需要提供上下文,并且你不能轻易的使用
apply来new。
// badconst x = [1, 2, 3, 4, 5];console.log.apply(console, x);// goodconst x = [1, 2, 3, 4, 5];console.log(...x);// badnew (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));// goodnew Date(...[2016, 8, 5]);
- 7.15 具有多行签名或者调用的函数应该像本指南中的其他多行列表一样缩进:在一行上只有一个条目,并且每个条目最后加上逗号。 eslint:
[function-paren-newline](https://eslint.org/docs/rules/function-paren-newline)```javascript // bad function foo(bar,
// … }baz,quux) {
// good function foo( bar, baz, quux, ) { // … }
// bad console.log(foo, bar, baz);
// good console.log( foo, bar, baz, );
<a name="05aa0031"></a>## 箭头函数- [8.1](#arrows--use-them) 当你必须使用匿名函数时 (当传递内联函数时), 使用箭头函数。 eslint: `[prefer-arrow-callback](https://eslint.org/docs/rules/prefer-arrow-callback.html)`, `[arrow-spacing](https://eslint.org/docs/rules/arrow-spacing.html)`> 为什么? 它创建了一个在 `this` 上下文中执行的函数版本,它通常是你想要的,并且是一个更简洁的语法。> 为什么不? 如果你有一个相当复杂的函数,你可以把这个逻辑转移到它自己的命名函数表达式中。```javascript// bad[1, 2, 3].map(function (x) {const y = x + 1;return x * y;});// good[1, 2, 3].map((x) => {const y = x + 1;return x * y;});
- 8.2 如果函数体包含一个单独的语句,返回一个没有副作用的 expression , 省略括号并使用隐式返回。否则,保留括号并使用
return语句。 eslint:[arrow-parens](https://eslint.org/docs/rules/arrow-parens.html),[arrow-body-style](https://eslint.org/docs/rules/arrow-body-style.html)为什么? 语法糖。 多个函数被链接在一起时,提高可读性。
// bad[1, 2, 3].map(number => {const nextNumber = number + 1;`A string containing the ${nextNumber}.`;});// good[1, 2, 3].map(number => `A string containing the ${number}.`);// good[1, 2, 3].map((number) => {const nextNumber = number + 1;return `A string containing the ${nextNumber}.`;});// good[1, 2, 3].map((number, index) => ({[index]: number,}));// 没有副作用的隐式返回function foo(callback) {const val = callback();if (val === true) {// 如果回调返回 true 执行}}let bool = false;// badfoo(() => bool = true);// goodfoo(() => {bool = true;});
- 8.3 如果表达式跨越多个行,用括号将其括起来,以获得更好的可读性。
为什么? 它清楚地显示了函数的起点和终点。
// bad['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(httpMagicObjectWithAVeryLongName,httpMethod,));// good['get', 'post', 'put'].map(httpMethod => (Object.prototype.hasOwnProperty.call(httpMagicObjectWithAVeryLongName,httpMethod,)));
- 8.4 如果你的函数接收一个参数,则可以不用括号,省略括号。 否则,为了保证清晰和一致性,需要在参数周围加上括号。 注意:总是使用括号是可以接受的,在这种情况下,我们使用 “always” option 来配置 eslint. eslint:
[arrow-parens](https://eslint.org/docs/rules/arrow-parens.html)为什么? 减少视觉上的混乱。
// bad[1, 2, 3].map((x) => x * x);// good[1, 2, 3].map(x => x * x);// good[1, 2, 3].map(number => (`A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!`));// bad[1, 2, 3].map(x => {const y = x + 1;return x * y;});// good[1, 2, 3].map((x) => {const y = x + 1;return x * y;});
- 8.5 避免箭头函数符号 (
=>) 和比较运算符 (<=,>=) 的混淆。 eslint:[no-confusing-arrow](https://eslint.org/docs/rules/no-confusing-arrow)```javascript // bad const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize;
// bad const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize;
// good const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize);
// good const itemHeight = (item) => { const { height, largeSize, smallSize } = item; return height > 256 ? largeSize : smallSize; };
- [8.6](#whitespace--implicit-arrow-linebreak) 注意带有隐式返回的箭头函数函数体的位置。 eslint: `[implicit-arrow-linebreak](https://eslint.org/docs/rules/implicit-arrow-linebreak)````javascript// bad(foo) =>bar;(foo) =>(bar);// good(foo) => bar;(foo) => (bar);(foo) => (bar)
类和构造器
- 9.1 尽量使用
class. 避免直接操作prototype.为什么?
class语法更简洁,更容易推理。
// badfunction Queue(contents = []) {this.queue = [...contents];}Queue.prototype.pop = function () {const value = this.queue[0];this.queue.splice(0, 1);return value;};// goodclass Queue {constructor(contents = []) {this.queue = [...contents];}pop() {const value = this.queue[0];this.queue.splice(0, 1);return value;}}
- 9.2 使用
extends来扩展继承。为什么? 它是一个内置的方法,可以在不破坏
instanceof的情况下继承原型功能。
// badconst inherits = require('inherits');function PeekableQueue(contents) {Queue.apply(this, contents);}inherits(PeekableQueue, Queue);PeekableQueue.prototype.peek = function () {return this.queue[0];};// goodclass PeekableQueue extends Queue {peek() {return this.queue[0];}}
- 9.3 方法返回了
this来供其内部方法调用。 ```javascript // bad Jedi.prototype.jump = function () { this.jumping = true; return true; };
Jedi.prototype.setHeight = function (height) { this.height = height; };
const luke = new Jedi(); luke.jump(); // => true luke.setHeight(20); // => undefined
// good class Jedi { jump() { this.jumping = true; return this; }
setHeight(height) { this.height = height; return this; } }
const luke = new Jedi();
luke.jump() .setHeight(20);
- [9.4](#constructors--tostring) 只要在确保能正常工作并且不产生任何副作用的情况下,编写一个自定义的 `toString()` 方法也是可以的。```javascriptclass Jedi {constructor(options = {}) {this.name = options.name || 'no name';}getName() {return this.name;}toString() {return `Jedi - ${this.getName()}`;}}
9.5 如果没有指定类,则类具有默认的构造器。 一个空的构造器或是一个代表父类的函数是没有必要的。 eslint:
[no-useless-constructor](https://eslint.org/docs/rules/no-useless-constructor)```javascript // bad class Jedi { constructor() {}getName() { return this.name; } }
// bad class Rey extends Jedi { constructor(…args) { super(…args); } }
// good class Rey extends Jedi { constructor(…args) { super(…args); this.name = ‘Rey’; } }
- [9.6](#classes--no-duplicate-members) 避免定义重复的类成员。 eslint: `[no-dupe-class-members](https://eslint.org/docs/rules/no-dupe-class-members)`> 为什么? 重复的类成员声明将会默认倾向于最后一个 - 具有重复的类成员可以说是一个错误。```javascript// badclass Foo {bar() { return 1; }bar() { return 2; }}// goodclass Foo {bar() { return 1; }}// goodclass Foo {bar() { return 2; }}
[
](#table-of-contents)
模块
- 10.1 你可能经常使用模块 (
import/export) 在一些非标准模块的系统上。 你也可以在你喜欢的模块系统上相互转换。为什么? 模块是未来的趋势,让我们拥抱未来。
// badconst AirbnbStyleGuide = require('./AirbnbStyleGuide');module.exports = AirbnbStyleGuide.es6;// okimport AirbnbStyleGuide from './AirbnbStyleGuide';export default AirbnbStyleGuide.es6;// bestimport { es6 } from './AirbnbStyleGuide';export default es6;
- 10.2 不要使用通配符导入。
为什么? 这确定你有一个单独的默认导出。
// badimport * as AirbnbStyleGuide from './AirbnbStyleGuide';// goodimport AirbnbStyleGuide from './AirbnbStyleGuide';
- 10.3 不要直接从导入导出。
为什么? 虽然写在一行很简洁,但是有一个明确的导入和一个明确的导出能够保证一致性。
// bad// filename es6.jsexport { es6 as default } from './AirbnbStyleGuide';// good// filename es6.jsimport { es6 } from './AirbnbStyleGuide';export default es6;
- 10.4 只从一个路径导入所有需要的东西。
eslint:[no-duplicate-imports](https://eslint.org/docs/rules/no-duplicate-imports)为什么? 从同一个路径导入多个行,使代码更难以维护。
// badimport foo from 'foo';// … 其他导入 … //import { named1, named2 } from 'foo';// goodimport foo, { named1, named2 } from 'foo';// goodimport foo, {named1,named2,} from 'foo';
- 10.5 不要导出可变的引用。
eslint:[import/no-mutable-exports](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-mutable-exports.md)为什么? 在一般情况下,应该避免发生突变,但是在导出可变引用时及其容易发生突变。虽然在某些特殊情况下,可能需要这样,但是一般情况下只需要导出常量引用。
// badlet foo = 3;export { foo };// goodconst foo = 3;export { foo };
- 10.6 在单个导出的模块中,选择默认模块而不是指定的导出。
eslint:[import/prefer-default-export](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/prefer-default-export.md)为什么? 为了鼓励更多的文件只导出一件东西,这样可读性和可维护性更好。
// badexport function foo() {}// goodexport default function foo() {}
- 10.7 将所有的
imports 语句放在所有非导入语句的上边。
eslint:[import/first](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/first.md)为什么? 由于所有的
imports 都被提前,保持他们在顶部是为了防止意外发生。
// badimport foo from 'foo';foo.init();import bar from 'bar';// goodimport foo from 'foo';import bar from 'bar';foo.init();
- 10.8 多行导入应该像多行数组和对象一样缩进。
为什么? 花括号和其他规范一样,遵循相同的缩进规则,后边的都好一样。
// badimport {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';// goodimport {longNameA,longNameB,longNameC,longNameD,longNameE,} from 'path';
- 10.9 在模块导入语句中禁止使用 Webpack 加载器语法。
eslint:[import/no-webpack-loader-syntax](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-webpack-loader-syntax.md)为什么? 因为在导入语句中使用 webpack 语法,将代码和模块绑定在一起。应该在
webpack.config.js中使用加载器语法。
// badimport fooSass from 'css!sass!foo.scss';import barCss from 'style!css!bar.css';// goodimport fooSass from 'foo.scss';import barCss from 'bar.css';
迭代器和发生器
- 11.1 不要使用迭代器。 你应该使用 JavaScript 的高阶函数代替
for-in或者for-of。 eslint:[no-iterator](https://eslint.org/docs/rules/no-iterator.html)[no-restricted-syntax](https://eslint.org/docs/rules/no-restricted-syntax)为什么? 这是我们强制的规则。 拥有返回值得纯函数比这个更容易解释。 使用
map()/every()/filter()/find()/findIndex()/reduce()/some()/ … 遍历数组, 和使用Object.keys()/Object.values()/Object.entries()迭代你的对象生成数组。
const numbers = [1, 2, 3, 4, 5];// badlet sum = 0;for (let num of numbers) {sum += num;}sum === 15;// goodlet sum = 0;numbers.forEach((num) => {sum += num;});sum === 15;// best (use the functional force)const sum = numbers.reduce((total, num) => total + num, 0);sum === 15;// badconst increasedByOne = [];for (let i = 0; i < numbers.length; i++) {increasedByOne.push(numbers[i] + 1);}// goodconst increasedByOne = [];numbers.forEach((num) => {increasedByOne.push(num + 1);});// best (keeping it functional)const increasedByOne = numbers.map(num => num + 1);
- 11.2 不要使用发生器。
为什么? 它们不能很好的适应 ES5。
- 11.3 如果你必须使用发生器或者无视 我们的建议,请确保他们的函数签名是正常的间隔。 eslint:
[generator-star-spacing](https://eslint.org/docs/rules/generator-star-spacing)为什么?
function和*是同一个概念关键字的一部分 -*不是function的修饰符,function*是一个不同于function的构造器。
// badfunction * foo() {// ...}// badconst bar = function * () {// ...};// badconst baz = function *() {// ...};// badconst quux = function*() {// ...};// badfunction*foo() {// ...}// badfunction *foo() {// ...}// very badfunction*foo() {// ...}// very badconst wat = function*() {// ...};// goodfunction* foo() {// ...}// goodconst foo = function* () {// ...};
属性
- 12.1 访问属性时使用点符号。 eslint:
[dot-notation](https://eslint.org/docs/rules/dot-notation.html)```javascript const luke = { jedi: true, age: 28, };
// bad const isJedi = luke[‘jedi’];
// good const isJedi = luke.jedi;
- [12.2](#properties--bracket) 使用变量访问属性时,使用 `[]`表示法。```javascriptconst luke = {jedi: true,age: 28,};function getProp(prop) {return luke[prop];}const isJedi = getProp('jedi');
- 12.3 计算指数时,可以使用
**运算符。 eslint:[no-restricted-properties](https://eslint.org/docs/rules/no-restricted-properties). ```javascript // bad const binary = Math.pow(2, 10);
// good const binary = 2 ** 10;
<a name="ccc56781"></a>## 变量- [13.1](#variables--const) 使用 `const` 或者 `let` 来定义变量。 不这样做将创建一个全局变量。 我们希望避免污染全局命名空间。 Captain Planet 警告过我们。 eslint: `[no-undef](https://eslint.org/docs/rules/no-undef)` `[prefer-const](https://eslint.org/docs/rules/prefer-const)````javascript// badsuperPower = new SuperPower();// goodconst superPower = new SuperPower();
- 13.2 使用
const或者let声明每一个变量。 eslint:[one-var](https://eslint.org/docs/rules/one-var.html)为什么? 这样更容易添加新的变量声明,而且你不必担心是使用
;还是使用,或引入标点符号的差别。 你可以通过 debugger 逐步查看每个声明,而不是立即跳过所有声明。
// badconst items = getItems(),goSportsTeam = true,dragonball = 'z';// bad// (compare to above, and try to spot the mistake)const items = getItems(),goSportsTeam = true;dragonball = 'z';// goodconst items = getItems();const goSportsTeam = true;const dragonball = 'z';
- 13.3 把
const声明的放在一起,把let声明的放在一起。.为什么? 这在后边如果需要根据前边的赋值变量指定一个变量时很有用。
// badlet i, len, dragonball,items = getItems(),goSportsTeam = true;// badlet i;const items = getItems();let dragonball;const goSportsTeam = true;let len;// goodconst goSportsTeam = true;const items = getItems();let dragonball;let i;let length;
- 13.4 在你需要的使用定义变量,但是要把它们放在一个合理的地方。
为什么?
let和const是块级作用域而不是函数作用域。
// bad - 不必要的函数调用function checkName(hasName) {const name = getName();if (hasName === 'test') {return false;}if (name === 'test') {this.setName('');return false;}return name;}// goodfunction checkName(hasName) {if (hasName === 'test') {return false;}const name = getName();if (name === 'test') {this.setName('');return false;}return name;}
- 13.5 不要链式变量赋值。 eslint:
[no-multi-assign](https://eslint.org/docs/rules/no-multi-assign)为什么? 链式变量赋值会创建隐式全局变量。
// bad(function example() {// JavaScript 把它解释为// let a = ( b = ( c = 1 ) );// let 关键词只适用于变量 a ;变量 b 和变量 c 则变成了全局变量。let a = b = c = 1;}());console.log(a); // throws ReferenceErrorconsole.log(b); // 1console.log(c); // 1// good(function example() {let a = 1;let b = a;let c = a;}());console.log(a); // throws ReferenceErrorconsole.log(b); // throws ReferenceErrorconsole.log(c); // throws ReferenceError// 对于 `const` 也一样
- 13.6 避免使用不必要的递增和递减 (
++,--)。 eslint[no-plusplus](https://eslint.org/docs/rules/no-plusplus)为什么? 在eslint文档中,一元递增和递减语句以自动分号插入为主题,并且在应用程序中可能会导致默认值的递增或递减。它还可以用像
num += 1这样的语句来改变您的值,而不是使用num++或num ++。不允许不必要的增量和减量语句也会使您无法预先递增/预递减值,这也会导致程序中的意外行为。
// badconst array = [1, 2, 3];let num = 1;num++;--num;let sum = 0;let truthyCount = 0;for (let i = 0; i < array.length; i++) {let value = array[i];sum += value;if (value) {truthyCount++;}}// goodconst array = [1, 2, 3];let num = 1;num += 1;num -= 1;const sum = array.reduce((a, b) => a + b, 0);const truthyCount = array.filter(Boolean).length;
- 13.7 避免在赋值语句
=前后换行。如果你的代码违反了[max-len](https://eslint.org/docs/rules/max-len.html), 使用括号包裹。 eslint[operator-linebreak](https://eslint.org/docs/rules/operator-linebreak.html).为什么? 在
=前后换行,可能混淆赋的值。
// badconst foo =superLongLongLongLongLongLongLongLongFunctionName();// badconst foo= 'superLongLongLongLongLongLongLongLongString';// goodconst foo = (superLongLongLongLongLongLongLongLongFunctionName());// goodconst foo = 'superLongLongLongLongLongLongLongLongString';
提升
- 14.1
var定义的变量会被提升到函数范围的最顶部,但是它的赋值不会。const和let声明的变量受到一个称之为 Temporal Dead Zones (TDZ) 的新概念保护。 知道为什么 typeof 不再安全 是很重要的。 ```javascript // 我们知道这个行不通 (假设没有未定义的全局变量) function example() { console.log(notDefined); // => throws a ReferenceError }
// 在引用变量后创建变量声明将会因变量提升而起作用。
// 注意: 真正的值 true 不会被提升。
function example() {
console.log(declaredButNotAssigned); // => undefined
var declaredButNotAssigned = true;
}
// 解释器将变量提升到函数的顶部 // 这意味着我们可以将上边的例子重写为: function example() { let declaredButNotAssigned; console.log(declaredButNotAssigned); // => undefined declaredButNotAssigned = true; }
// 使用 const 和 let function example() { console.log(declaredButNotAssigned); // => throws a ReferenceError console.log(typeof declaredButNotAssigned); // => throws a ReferenceError const declaredButNotAssigned = true; }
- [14.2](#hoisting--anon-expressions) 匿名函数表达式提升变量名,而不是函数赋值。```javascriptfunction example() {console.log(anonymous); // => undefinedanonymous(); // => TypeError anonymous is not a functionvar anonymous = function () {console.log('anonymous function expression');};}
14.3 命名函数表达式提升的是变量名,而不是函数名或者函数体。 ```javascript function example() { console.log(named); // => undefined
named(); // => TypeError named is not a function
superPower(); // => ReferenceError superPower is not defined
var named = function superPower() { console.log(‘Flying’); }; }
// 当函数名和变量名相同时也是如此。 function example() { console.log(named); // => undefined
named(); // => TypeError named is not a function
var named = function named() { console.log(‘named’); }; }
- [14.4](#hoisting--declarations) 函数声明提升其名称和函数体。```javascriptfunction example() {superPower(); // => Flyingfunction superPower() {console.log('Flying');}}
- 更多信息请参考 Ben Cherry 的 JavaScript Scoping & Hoisting。
比较运算符和等号
- 15.1 使用
===和!==而不是==和!=。 eslint:[eqeqeq](https://eslint.org/docs/rules/eqeqeq.html)
- 15.2 条件语句,例如
if语句使用ToBoolean的抽象方法来计算表达式的结果,并始终遵循以下简单的规则:- Objects 的取值为: true
- Undefined 的取值为: false
- Null 的取值为: false
- Booleans 的取值为: 布尔值的取值
- Numbers 的取值为:如果为 +0, -0, or NaN 值为 false 否则为 true
- Strings 的取值为: 如果是一个空字符串
''值为 false 否则为 trueif ([0] && []) {// true// 一个数组(即使是空的)是一个对象,对象的取值为 true}
- 15.3 对于布尔值使用简写,但是对于字符串和数字进行显式比较。 ```javascript // bad if (isValid === true) { // … }
// good if (isValid) { // … }
// bad if (name) { // … }
// good if (name !== ‘’) { // … }
// bad if (collection.length) { // … }
// good if (collection.length > 0) { // … }
- [15.4](#comparison--moreinfo) 获取更多信息请查看 Angus Croll 的 [Truth Equality and JavaScript](https://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/#more-2108) 。- [15.5](#comparison--switch-blocks) 在 `case` 和 `default` 的子句中,如果存在声明 (例如. `let`, `const`, `function`, 和 `class`),使用大括号来创建块 。 eslint: `[no-case-declarations](https://eslint.org/docs/rules/no-case-declarations.html)`> 为什么? 语法声明在整个 `switch` 块中都是可见的,但是只有在赋值的时候才会被初始化,这种情况只有在 `case` 条件达到才会发生。 当多个 `case` 语句定义相同的东西是,这会导致问题问题。```javascript// badswitch (foo) {case 1:let x = 1;break;case 2:const y = 2;break;case 3:function f() {// ...}break;default:class C {}}// goodswitch (foo) {case 1: {let x = 1;break;}case 2: {const y = 2;break;}case 3: {function f() {// ...}break;}case 4:bar();break;default: {class C {}}}
- 15.6 三目表达式不应该嵌套,通常是单行表达式。 eslint:
[no-nested-ternary](https://eslint.org/docs/rules/no-nested-ternary.html)```javascript // bad const foo = maybe1 > maybe2 ? “bar” : value1 > value2 ? “baz” : null;
// 分离为两个三目表达式 const maybeNull = value1 > value2 ? ‘baz’ : null;
// better const foo = maybe1 > maybe2 ? ‘bar’ : maybeNull;
// best const foo = maybe1 > maybe2 ? ‘bar’ : maybeNull;
- [15.7](#comparison--unneeded-ternary) 避免不必要的三目表达式。 eslint: `[no-unneeded-ternary](https://eslint.org/docs/rules/no-unneeded-ternary.html)````javascript// badconst foo = a ? a : b;const bar = c ? true : false;const baz = c ? false : true;// goodconst foo = a || b;const bar = !!c;const baz = !c;
- 15.8 使用该混合运算符时,使用括号括起来。 唯一例外的是标准算数运算符 (
+,-,*, &/) 因为他们的优先级被广泛理解。 eslint:[no-mixed-operators](https://eslint.org/docs/rules/no-mixed-operators.html)为什么? 这能提高可读性并且表明开发人员的意图。
// badconst foo = a && b < 0 || c > 0 || d + 1 === 0;// badconst bar = a ** b - 5 % d;// bad// 可能陷入一种 (a || b) && c 的思考if (a || b && c) {return d;}// goodconst foo = (a && b < 0) || c > 0 || (d + 1 === 0);// goodconst bar = (a ** b) - (5 % d);// goodif (a || (b && c)) {return d;}// goodconst bar = a + b / c * d;
块
- 16.1 当有多行代码块的时候,使用大括号包裹。 eslint:
[nonblock-statement-body-position](https://eslint.org/docs/rules/nonblock-statement-body-position)```javascript // bad if (test) return false;
// good if (test) return false;
// good if (test) { return false; }
// bad function foo() { return false; }
// good function bar() { return false; }
- [16.2](#blocks--cuddled-elses) 如果你使用的是 `if` 和 `else` 的多行代码块,则将 `else` 语句放在 `if` 块闭括号同一行的位置。 eslint: `[brace-style](https://eslint.org/docs/rules/brace-style.html)````javascript// badif (test) {thing1();thing2();}else {thing3();}// goodif (test) {thing1();thing2();} else {thing3();}
- 16.3 如果一个
if块总是执行一个return语句,那么接下来的else块就没有必要了。 如果一个包含return语句的else if块,在一个包含了return语句的if块之后,那么可以拆成多个if块。 eslint:[no-else-return](https://eslint.org/docs/rules/no-else-return)```javascript // bad function foo() { if (x) { return x; } else { return y; } }
// bad function cats() { if (x) { return x; } else if (y) { return y; } }
// bad function dogs() { if (x) { return x; } else { if (y) { return y; } } }
// good function foo() { if (x) { return x; }
return y; }
// good function cats() { if (x) { return x; }
if (y) { return y; } }
// good function dogs(x) { if (x) { if (z) { return y; } } else { return z; } }
<a name="ea5ade1a"></a>## 控制语句- [17.1](#control-statements) 如果你的控制语句 (`if`, `while` 等) 太长或者超过了一行最大长度的限制,则可以将每个条件(或组)放入一个新的行。 逻辑运算符应该在行的开始。> 为什么? 要求操作符在行的开始保持对齐并遵循类似方法衔接的模式。 这提高了可读性,并且使更复杂的逻辑更容易直观的被理解。```javascript// badif ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {thing1();}// badif (foo === 123 &&bar === 'abc') {thing1();}// badif (foo === 123&& bar === 'abc') {thing1();}// badif (foo === 123 &&bar === 'abc') {thing1();}// goodif (foo === 123&& bar === 'abc') {thing1();}// goodif ((foo === 123 || bar === 'abc')&& doesItLookGoodWhenItBecomesThatLong()&& isThisReallyHappening()) {thing1();}// goodif (foo === 123 && bar === 'abc') {thing1();}
- 17.2 不要使用选择操作符代替控制语句。 ```javascript // bad !isRunning && startRunning();
// good if (!isRunning) { startRunning(); }
<a name="9f1f57ca"></a>## 注释- [18.1](#comments--multiline) 使用 `/** ... */` 来进行多行注释。```javascript// bad// make() returns a new element// based on the passed in tag name//// @param {String} tag// @return {Element} elementfunction make(tag) {// ...return element;}// good/*** make() returns a new element* based on the passed-in tag name*/function make(tag) {// ...return element;}
- 18.2 使用
//进行单行注释。 将单行注释放在需要注释的行的上方新行。 在注释之前放一个空行,除非它在块的第一行。 ```javascript // bad const active = true; // is current tab
// good // is current tab const active = true;
// bad function getType() { console.log(‘fetching type…’); // set the default type to ‘no type’ const type = this.type || ‘no type’;
return type; }
// good function getType() { console.log(‘fetching type…’);
// set the default type to ‘no type’ const type = this.type || ‘no type’;
return type; }
// also good function getType() { // set the default type to ‘no type’ const type = this.type || ‘no type’;
return type; }
- [18.3](#comments--spaces) 用一个空格开始所有的注释,使它更容易阅读。 eslint: `[spaced-comment](https://eslint.org/docs/rules/spaced-comment)````javascript// bad//is current tabconst active = true;// good// is current tabconst active = true;// bad/***make() returns a new element*based on the passed-in tag name*/function make(tag) {// ...return element;}// good/*** make() returns a new element* based on the passed-in tag name*/function make(tag) {// ...return element;}
- 18.4 使用
FIXME或者TODO开始你的注释可以帮助其他开发人员快速了解,如果你提出了一个需要重新审视的问题,或者你对需要实现的问题提出的解决方案。 这些不同于其他评论,因为他们是可操作的。 这些行为是FIXME: -- 需要解决这个问题或者TODO: -- 需要被实现。
18.5 使用
// FIXME:注释一个问题。class Calculator extends Abacus {constructor() {super();// FIXME: 这里不应该使用全局变量total = 0;}}
18.6 使用
// TODO:注释解决问题的方法。class Calculator extends Abacus {constructor() {super();// TODO: total 应该由一个 param 的选项配置this.total = 0;}}
空白
- 19.1 使用 tabs (空格字符) 设置为 2 个空格。 eslint:
[indent](https://eslint.org/docs/rules/indent.html)```javascript // bad function foo() { ∙∙∙∙let name; }
// bad function bar() { ∙let name; }
// good function baz() { ∙∙let name; }
- [19.2](#whitespace--before-blocks) 在主体前放置一个空格。 eslint: `[space-before-blocks](https://eslint.org/docs/rules/space-before-blocks.html)````javascript// badfunction test(){console.log('test');}// goodfunction test() {console.log('test');}// baddog.set('attr',{age: '1 year',breed: 'Bernese Mountain Dog',});// gooddog.set('attr', {age: '1 year',breed: 'Bernese Mountain Dog',});
- 19.3 在控制语句(
if,while等)开始括号之前放置一个空格。 在函数调用和是声明中,在参数列表和函数名之间没有空格。 eslint:[keyword-spacing](https://eslint.org/docs/rules/keyword-spacing.html)```javascript // bad if(isJedi) { fight (); }
// good if (isJedi) { fight(); }
// bad function fight () { console.log (‘Swooosh!’); }
// good function fight() { console.log(‘Swooosh!’); }
- [19.4](#whitespace--infix-ops) 用空格分离操作符。 eslint: `[space-infix-ops](https://eslint.org/docs/rules/space-infix-ops.html)````javascript// badconst x=y+5;// goodconst x = y + 5;
- 19.5 使用单个换行符结束文件。 eslint:
[eol-last](https://github.com/eslint/eslint/blob/master/docs/rules/eol-last.md)// badimport { es6 } from './AirbnbStyleGuide';// ...export default es6;
// badimport { es6 } from './AirbnbStyleGuide';// ...export default es6;↵↵
// goodimport { es6 } from './AirbnbStyleGuide';// ...export default es6;↵
- 19.6 在使用链式方法调用的时候使用缩进(超过两个方法链)。 使用一个引导点,强调该行是方法调用,而不是新的语句。 eslint:
[newline-per-chained-call](https://eslint.org/docs/rules/newline-per-chained-call)[no-whitespace-before-property](https://eslint.org/docs/rules/no-whitespace-before-property)```javascript // bad $(‘#items’).find(‘.selected’).highlight().end().find(‘.open’).updateCount();
// bad $(‘#items’). find(‘.selected’). highlight(). end(). find(‘.open’). updateCount();
// good $(‘#items’) .find(‘.selected’) .highlight() .end() .find(‘.open’) .updateCount();
// bad
const leds = stage.selectAll(‘.led’).data(data).enter().append(‘svg:svg’).classed(‘led’, true)
.attr(‘width’, (radius + margin) * 2).append(‘svg:g’)
.attr(‘transform’, translate(${radius + margin},${radius + margin}))
.call(tron.led);
// good
const leds = stage.selectAll(‘.led’)
.data(data)
.enter().append(‘svg:svg’)
.classed(‘led’, true)
.attr(‘width’, (radius + margin) * 2)
.append(‘svg:g’)
.attr(‘transform’, translate(${radius + margin},${radius + margin}))
.call(tron.led);
// good const leds = stage.selectAll(‘.led’).data(data);
- [19.7](#whitespace--after-blocks) 在块和下一个语句之前留下一空白行。```javascript// badif (foo) {return bar;}return baz;// goodif (foo) {return bar;}return baz;// badconst obj = {foo() {},bar() {},};return obj;// goodconst obj = {foo() {},bar() {},};return obj;// badconst arr = [function foo() {},function bar() {},];return arr;// goodconst arr = [function foo() {},function bar() {},];return arr;
19.8 不要在块的开头使用空白行。 eslint:
[padded-blocks](https://eslint.org/docs/rules/padded-blocks.html)```javascript // bad function bar() {console.log(foo);
}
// bad if (baz) {
console.log(qux); } else { console.log(foo);
}
// bad class Foo {
constructor(bar) { this.bar = bar; } }
// good function bar() { console.log(foo); }
// good if (baz) { console.log(qux); } else { console.log(foo); }
- [19.9](#whitespace--in-parens) 不要在括号内添加空格。 eslint: `[space-in-parens](https://eslint.org/docs/rules/space-in-parens.html)````javascript// badfunction bar( foo ) {return foo;}// goodfunction bar(foo) {return foo;}// badif ( foo ) {console.log(foo);}// goodif (foo) {console.log(foo);}
- 19.10 不要在中括号中添加空格。 eslint:
[array-bracket-spacing](https://eslint.org/docs/rules/array-bracket-spacing.html)```javascript // bad const foo = [ 1, 2, 3 ]; console.log(foo[ 0 ]);
// good const foo = [1, 2, 3]; console.log(foo[0]);
- [19.11](#whitespace--in-braces) 在花括号内添加空格。 eslint: `[object-curly-spacing](https://eslint.org/docs/rules/object-curly-spacing.html)````javascript// badconst foo = {clark: 'kent'};// goodconst foo = { clark: 'kent' };
- 19.12 避免让你的代码行超过100个字符(包括空格)。 注意:根据上边的 约束,长字符串可免除此规定,不应分解。 eslint:
[max-len](https://eslint.org/docs/rules/max-len.html)为什么? 这样能够确保可读性和可维护性。
// badconst foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy;// bad$.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.'));// goodconst foo = jsonData&& jsonData.foo&& jsonData.foo.bar&& jsonData.foo.bar.baz&& jsonData.foo.bar.baz.quux&& jsonData.foo.bar.baz.quux.xyzzy;// good$.ajax({method: 'POST',url: 'https://airbnb.com/',data: { name: 'John' },}).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.'));
- 19.13 要求打开的块标志和同一行上的标志拥有一致的间距。此规则还会在同一行关闭的块标记和前边的标记强制实施一致的间距。 eslint:
[block-spacing](https://eslint.org/docs/rules/block-spacing)```javascript // bad function foo() {return true;} if (foo) { bar = 0;}
// good function foo() { return true; } if (foo) { bar = 0; }
- [19.14](#whitespace--comma-spacing) 逗号之前避免使用空格,逗号之后需要使用空格。eslint: `[comma-spacing](https://eslint.org/docs/rules/comma-spacing)````javascript// badvar foo = 1,bar = 2;var arr = [1 , 2];// goodvar foo = 1, bar = 2;var arr = [1, 2];
- 19.15 在计算属性之间强化间距。eslint:
[computed-property-spacing](https://eslint.org/docs/rules/computed-property-spacing)```javascript // bad obj[foo ] obj[ ‘foo’] var x = {[ b ]: a} obj[foo[ bar ]]
// good obj[foo] obj[‘foo’] var x = { [b]: a } obj[foo[bar]]
- [19.16](#whitespace--func-call-spacing) 在函数和它的调用之间强化间距。 eslint: `[func-call-spacing](https://eslint.org/docs/rules/func-call-spacing)````javascript// badfunc ();func();// goodfunc();
- 19.17 在对象的属性和值之间强化间距。 eslint:
[key-spacing](https://eslint.org/docs/rules/key-spacing)```javascript // bad var obj = { “foo” : 42 }; var obj2 = { “foo”:42 };
// good var obj = { “foo”: 42 };
- [19.18](#whitespace--no-trailing-spaces) 在行的末尾避免使用空格。 eslint: `[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)`- [19.19](#whitespace--no-multiple-empty-lines) 避免多个空行,并且只允许在文件末尾添加一个换行符。 eslint: `[no-multiple-empty-lines](https://eslint.org/docs/rules/no-multiple-empty-lines)````javascript// badvar x = 1;var y = 2;// goodvar x = 1;var y = 2;
逗号
- 20.1 逗号前置: 不行 eslint:
[comma-style](https://eslint.org/docs/rules/comma-style.html)```javascript // bad const story = [ once , upon , aTime ];
// good const story = [ once, upon, aTime, ];
// bad const hero = { firstName: ‘Ada’ , lastName: ‘Lovelace’ , birthYear: 1815 , superPower: ‘computers’ };
// good const hero = { firstName: ‘Ada’, lastName: ‘Lovelace’, birthYear: 1815, superPower: ‘computers’, };
- [20.2](#commas--dangling) 添加尾随逗号: **可以** eslint: `[comma-dangle](https://eslint.org/docs/rules/comma-dangle.html)`> 为什么? 这个将造成更清洁的 git 扩展差异。 另外,像 Babel 这样的编译器,会在转换后的代码中删除额外的尾随逗号,这意味着你不必担心在浏览器中后面的 [尾随逗号问题](https://github.com/airbnb/javascript/blob/es5-deprecated/es5/README.md#commas) 。```diff// bad - 没有尾随逗号的 git 差异const hero = {firstName: 'Florence',- lastName: 'Nightingale'+ lastName: 'Nightingale',+ inventorOf: ['coxcomb chart', 'modern nursing']};// good - 有尾随逗号的 git 差异const hero = {firstName: 'Florence',lastName: 'Nightingale',+ inventorOf: ['coxcomb chart', 'modern nursing'],};
// badconst hero = {firstName: 'Dana',lastName: 'Scully'};const heroes = ['Batman','Superman'];// goodconst hero = {firstName: 'Dana',lastName: 'Scully',};const heroes = ['Batman','Superman',];// badfunction createHero(firstName,lastName,inventorOf) {// does nothing}// goodfunction createHero(firstName,lastName,inventorOf,) {// does nothing}// good (注意逗号不能出现在 "rest" 元素后边)function createHero(firstName,lastName,inventorOf,...heroArgs) {// does nothing}// badcreateHero(firstName,lastName,inventorOf);// goodcreateHero(firstName,lastName,inventorOf,);// good (注意逗号不能出现在 "rest" 元素后边)createHero(firstName,lastName,inventorOf,...heroArgs);
分号
- 21.1 对 eslint:
[semi](https://eslint.org/docs/rules/semi.html)为什么? 当 JavaScript 遇见一个没有分号的换行符时,它会使用一个叫做 Automatic Semicolon Insertion 的规则来确定是否应该以换行符视为语句的结束,并且如果认为如此,会在代码中断前插入一个分号到代码中。 但是,ASI 包含了一些奇怪的行为,如果 JavaScript 错误的解释了你的换行符,你的代码将会中断。 随着新特性成为 JavaScript 的一部分,这些规则将变得更加复杂。 明确地终止你的语句,并配置你的 linter 以捕获缺少的分号将有助于防止你遇到的问题。
// bad - 可能异常const luke = {}const leia = {}[luke, leia].forEach(jedi => jedi.father = 'vader')// bad - 可能异常const reaction = "No! That's impossible!"(async function meanwhileOnTheFalcon() {// handle `leia`, `lando`, `chewie`, `r2`, `c3p0`// ...}())// bad - 返回 `undefined` 而不是下一行的值 - 当 `return` 单独一行的时候 ASI 总是会发生function foo() {return'search your feelings, you know it to be foo'}// goodconst luke = {};const leia = {};[luke, leia].forEach((jedi) => {jedi.father = 'vader';});// goodconst reaction = "No! That's impossible!";(async function meanwhileOnTheFalcon() {// handle `leia`, `lando`, `chewie`, `r2`, `c3p0`// ...}());// goodfunction foo() {return 'search your feelings, you know it to be foo';}
- 更多信息.
类型转换和强制类型转换
- 22.1 在语句开始前进行类型转换。
- 22.2 字符类型: eslint:
[no-new-wrappers](https://eslint.org/docs/rules/no-new-wrappers)```javascript // => this.reviewScore = 9;
// bad const totalScore = new String(this.reviewScore); // typeof totalScore is “object” not “string”
// bad const totalScore = this.reviewScore + ‘’; // invokes this.reviewScore.valueOf()
// bad const totalScore = this.reviewScore.toString(); // isn’t guaranteed to return a string
// good const totalScore = String(this.reviewScore);
- [22.3](#coercion--numbers) 数字类型:使用 `Number` 进行类型铸造和 `parseInt` 总是通过一个基数来解析一个字符串。 eslint: `[radix](https://eslint.org/docs/rules/radix)` `[no-new-wrappers](https://eslint.org/docs/rules/no-new-wrappers)````javascriptconst inputValue = '4';// badconst val = new Number(inputValue);// badconst val = +inputValue;// badconst val = inputValue >> 0;// badconst val = parseInt(inputValue);// goodconst val = Number(inputValue);// goodconst val = parseInt(inputValue, 10);
- 22.4 如果出于某种原因,你正在做一些疯狂的事情,而
parseInt是你的瓶颈,并且出于 性能问题 需要使用位运算, 请写下注释,说明为什么这样做和你做了什么。// good/*** parseInt 使我的代码变慢。* 位运算将一个字符串转换成数字更快。*/const val = inputValue >> 0;
- 22.5 注意: 当你使用位运算的时候要小心。 数字总是被以 64-bit 值 的形式表示,但是位运算总是返回一个 32-bit 的整数 (来源)。 对于大于 32 位的整数值,位运算可能会导致意外行为。讨论。 最大的 32 位整数是: 2,147,483,647。
2147483647 >> 0; // => 21474836472147483648 >> 0; // => -21474836482147483649 >> 0; // => -2147483647
- 22.6 布尔类型: eslint:
[no-new-wrappers](https://eslint.org/docs/rules/no-new-wrappers)```javascript const age = 0;
// bad const hasAge = new Boolean(age);
// good const hasAge = Boolean(age);
// best const hasAge = !!age;
<a name="ff332294"></a>## 命名规范- [23.1](#naming--descriptive) 避免单字母的名字。用你的命名来描述功能。 eslint: `[id-length](https://eslint.org/docs/rules/id-length)````javascript// badfunction q() {// ...}// goodfunction query() {// ...}
- 23.2 在命名对象、函数和实例时使用驼峰命名法(camelCase)。 eslint:
[camelcase](https://eslint.org/docs/rules/camelcase.html)```javascript // bad const OBJEcttsssss = {}; const this_is_my_object = {}; function c() {}
// good const thisIsMyObject = {}; function thisIsMyFunction() {}
- [23.3](#naming--PascalCase) 只有在命名构造器或者类的时候才用帕斯卡拼命名法(PascalCase)。 eslint: `[new-cap](https://eslint.org/docs/rules/new-cap.html)````javascript// badfunction user(options) {this.name = options.name;}const bad = new user({name: 'nope',});// goodclass User {constructor(options) {this.name = options.name;}}const good = new User({name: 'yup',});
- 23.4 不要使用前置或者后置下划线。 eslint:
[no-underscore-dangle](https://eslint.org/docs/rules/no-underscore-dangle.html)为什么? JavaScript 在属性和方法方面没有隐私设置。 虽然前置的下划线是一种常见的惯例,意思是 “private” ,事实上,这些属性时公开的,因此,它们也是你公共 API 的一部分。 这种约定可能导致开发人员错误的认为更改不会被视为中断,或者不需要测试。建议:如果你想要什么东西是 “private” , 那就一定不能有明显的表现。
// badthis.__firstName__ = 'Panda';this.firstName_ = 'Panda';this._firstName = 'Panda';// goodthis.firstName = 'Panda';// 好,在 WeakMapx 可用的环境中// see https://kangax.github.io/compat-table/es6/#test-WeakMapconst firstNames = new WeakMap();firstNames.set(this, 'Panda');
- 23.5 不要保存
this的引用。 使用箭头函数或者 函数#bind。 ```javascript // bad function foo() { const self = this; return function () { console.log(self); }; }
// bad function foo() { const that = this; return function () { console.log(that); }; }
// good function foo() { return () => { console.log(this); }; }
- [23.6](#naming--filename-matches-export) 文件名应该和默认导出的名称完全匹配。```javascript// file 1 contentsclass CheckBox {// ...}export default CheckBox;// file 2 contentsexport default function fortyTwo() { return 42; }// file 3 contentsexport default function insideDirectory() {}// in some other file// badimport CheckBox from './checkBox'; // PascalCase import/export, camelCase filenameimport FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase exportimport InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export// badimport CheckBox from './check_box'; // PascalCase import/export, snake_case filenameimport forty_two from './forty_two'; // snake_case import/filename, camelCase exportimport inside_directory from './inside_directory'; // snake_case import, camelCase exportimport index from './inside_directory/index'; // requiring the index file explicitlyimport insideDirectory from './insideDirectory/index'; // requiring the index file explicitly// goodimport CheckBox from './CheckBox'; // PascalCase export/import/filenameimport fortyTwo from './fortyTwo'; // camelCase export/import/filenameimport insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index"// ^ supports both insideDirectory.js and insideDirectory/index.js
- 23.7 当你导出默认函数时使用驼峰命名法。 你的文件名应该和方法名相同。 ```javascript function makeStyleGuide() { // … }
export default makeStyleGuide;
- [23.8](#naming--PascalCase-singleton) 当你导出一个构造器 / 类 / 单例 / 函数库 / 暴露的对象时应该使用帕斯卡命名法。```javascriptconst AirbnbStyleGuide = {es6: {},};export default AirbnbStyleGuide;
- 23.9 缩略词和缩写都必须是全部大写或者全部小写。
为什么? 名字是为了可读性,不是为了满足计算机算法。
// badimport SmsContainer from './containers/SmsContainer';// badconst HttpRequests = [// ...];// goodimport SMSContainer from './containers/SMSContainer';// goodconst HTTPRequests = [// ...];// also goodconst httpRequests = [// ...];// bestimport TextMessageContainer from './containers/TextMessageContainer';// bestconst requests = [// ...];
23.10 你可以大写一个常量,如果它:(1)被导出,(2)使用
const定义(不能被重新赋值),(3)程序员可以信任它(以及其嵌套的属性)是不变的。为什么? 这是一个可以帮助程序员确定变量是否会发生变化的辅助工具。UPPERCASE_VARIABLES 可以让程序员知道他们可以相信变量(及其属性)不会改变。
- 是否是对所有的
const定义的变量? - 这个是没有必要的,不应该在文件中使用大写。但是,它应该用于导出常量。 - 导出对象呢? - 在顶级导出属性 (e.g.
EXPORTED_OBJECT.key) 并且保持所有嵌套属性不变。 ```javascript // bad const PRIVATE_VARIABLE = ‘should not be unnecessarily uppercased within a file’;
- 是否是对所有的
// bad export const THING_TO_BE_CHANGED = ‘should obviously not be uppercased’;
// bad export let REASSIGNABLE_VARIABLE = ‘do not use let with uppercase variables’;
// —-
// 允许,但是不提供语义值 export const apiKey = ‘SOMEKEY’;
// 多数情况下,很好 export const API_KEY = ‘SOMEKEY’;
// —-
// bad - 不必要大写 key 没有增加语义值 export const MAPPING = { KEY: ‘value’ };
// good export const MAPPING = { key: ‘value’ };
<a name="fb1b0e8e"></a>## 存取器- [24.1](#accessors--not-required) 对于属性的的存取函数不是必须的。- [24.2](#accessors--no-getters-setters) 不要使用 JavaScript 的 getters/setters 方法,因为它们会导致意外的副作用,并且更加难以测试、维护和推敲。 相应的,如果你需要存取函数的时候使用 `getVal()` 和 `setVal('hello')`。```javascript// badclass Dragon {get age() {// ...}set age(value) {// ...}}// goodclass Dragon {getAge() {// ...}setAge(value) {// ...}}
- 24.3 如果属性/方法是一个
boolean值,使用isVal()或者hasVal()。 ```javascript // bad if (!dragon.age()) { return false; }
// good if (!dragon.hasAge()) { return false; }
- [24.4](#accessors--consistent) 可以创建 `get()` 和 `set()` 方法,但是要保证一致性。```javascriptclass Jedi {constructor(options = {}) {const lightsaber = options.lightsaber || 'blue';this.set('lightsaber', lightsaber);}set(key, val) {this[key] = val;}get(key) {return this[key];}}
事件
- 25.1 当给事件(无论是 DOM 事件还是更加私有的事件)附加数据时,传入一个对象(通畅也叫做 “hash” ) 而不是原始值。 这样可以让后边的贡献者向事件数据添加更多的数据,而不用找出更新事件的每个处理器。 例如,不好的写法: ```javascript // bad $(this).trigger(‘listingUpdated’, listing.id);
// …
$(this).on(‘listingUpdated’, (e, listingID) => { // do something with listingID });
- 更好的写法:```javascript// good$(this).trigger('listingUpdated', { listingID: listing.id });// ...$(this).on('listingUpdated', (e, data) => {// do something with data.listingID});
ECMAScript 5 兼容性
ECMAScript 6+ (ES 2015+) Styles
- 28.1 这是一个链接到各种 ES6+ 特性的集合。
标准库
标准库
包含功能已损坏的实用工具,但因为遗留原因而保留。
- 29.1 使用
Number.isNaN代替全局的isNaN.
eslint:[no-restricted-globals](https://eslint.org/docs/rules/no-restricted-globals)为什么? 全局的
isNaN强制非数字转化为数字,对任何强制转化为 NaN 的东西都返回 true。 如果需要这种行为,请明确说明。
// badisNaN('1.2'); // falseisNaN('1.2.3'); // true// goodNumber.isNaN('1.2.3'); // falseNumber.isNaN(Number('1.2.3')); // true
- 29.2 使用
Number.isFinite代替全局的isFinite.
eslint:[no-restricted-globals](https://eslint.org/docs/rules/no-restricted-globals)为什么? 全局的
isFinite强制非数字转化为数字,对任何强制转化为有限数字的东西都返回 true。 如果需要这种行为,请明确说明。
// badisFinite('2e3'); // true// goodNumber.isFinite('2e3'); // falseNumber.isFinite(parseInt('2e3', 10)); // true
性能
- On Layout & Web Performance
- String vs Array Concat
- Try/Catch Cost In a Loop
- Bang Function
- jQuery Find vs Context, Selector
- innerHTML vs textContent for script text
- Long String Concatenation
- Are Javascript functions like
[map()](https://www.quora.com/JavaScript-programming-language-Are-Javascript-functions-like-map-reduce-and-filter-already-optimized-for-traversing-array/answer/Quildreen-Motta),[reduce()](https://www.quora.com/JavaScript-programming-language-Are-Javascript-functions-like-map-reduce-and-filter-already-optimized-for-traversing-array/answer/Quildreen-Motta), and[filter()](https://www.quora.com/JavaScript-programming-language-Are-Javascript-functions-like-map-reduce-and-filter-already-optimized-for-traversing-array/answer/Quildreen-Motta)optimized for traversing arrays? - Loading…
[
](#table-of-contents)
资源
学习 ES6+
读这个
工具
- Code Style Linters
- Neutrino preset - neutrino-preset-airbnb-base
其他编码规范
- Google JavaScript Style Guide
- jQuery Core Style Guidelines
- Principles of Writing Consistent, Idiomatic JavaScript
- StandardJS
其他风格
- Naming this in nested functions - Christian Johansen
- Conditional Callbacks - Ross Allen
- Popular JavaScript Coding Conventions on GitHub - JeongHoon Byun
- Multiple var statements in JavaScript, not superfluous - Ben Alman
