类型
1.1 基本类型: 直接获取到基本类型值
string
number
boolean
null
undefined
symbol
bigint
const foo = 1;let bar = foo;bar = 9;console.log(foo, bar); // => 1, 9
由于 Symbols 和 BigInts 不能被正确的 polyfill。所以不应在不能原生支持这些类型的环境或浏览器中使用他们。
1.2 复杂类型: 复杂类型赋值是获取到他的引用的值。
object
array
function
const foo = [1, 2];const bar = foo;bar[0] = 9;console.log(foo[0], bar[0]); // => 9, 9
引用
2.1 所有的赋值都用 const,避免使用 var。eslint: prefer-const, no-const-assign
这个能确保你不会改变你的初始值,重复引用会导致 bug 并且使代码变得难以理解。
// badvar a = 1;var b = 2;// goodconst a = 1;const b = 2;
2.2 如果你一定要对参数重新赋值,使用 let,而不是 var。eslint: no-var
为什么?因为 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
对象
3.1 使用字面值创建对象。eslint: no-new-object
// badconst item = new Object();// goodconst item = {};
3.2 使用计算属性名创建一个带有动态属性名的对象。
为什么?因为这可以使你在同一个地方定义所有对象属性。
function 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
// badconst atom = {value: 1,addValue: function (value) {return atom.value + value;},};// goodconst atom = {value: 1,// 对象的方法addValue(value) {return atom.value + value;},};
3.4 用属性值缩写。eslint: object-shorthand
为什么?因为这样写的更少且可读性更高。
const 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
为什么?通常我们认为这种方式主观上更易读。不仅优化了代码高亮,而且也更容易被许多 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。获取对象指定的几个属性时,用对象的 rest 解构运算符(即 … 运算符)更好。eslint: prefer-object-spread
// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates original ಠ_ಠ
delete copy.a; // so does this
// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
// good es6 扩展运算符 …
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 }
数组
4.1 用字面量创建数组。eslint: no-array-constructor
// badconst items = new Array();// goodconst items = [];
4.2 用 Array#push 代替直接向数组中添加一个值。
const someStack = [];// badsomeStack[someStack.length] = 'abracadabra';// goodsomeStack.push('abracadabra');
4.3 用扩展运算符做数组浅拷贝,类似上面的对象浅拷贝。
// 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 来将一个可迭代的对象转换成数组。
const foo = document.querySelectorAll('.foo');// goodconst nodes = Array.from(foo);// bestconst nodes = [...foo];
4.5 用 Array.from 将一个类数组对象转成一个数组。
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };// badconst arr = Array.prototype.slice.call(arrLike);// goodconst arr = Array.from(arrLike);
4.6 用 Array.from 而不是 … 运算符去做 map 遍历。 因为这样可以避免创建一个临时数组。
// badconst baz = [...foo].map(bar);// goodconst baz = Array.from(foo, bar);
4.7 在数组方法的回调函数中使用 return 语句。如果函数体由一条返回一个表达式的语句组成,并且这个表达式没有副作用, 这个时候可以忽略 return,详见 8.2。eslint: array-callback-return
// good[1, 2, 3].map((x) => {const y = x + 1;return x * y;});// good 函数只有一个语句[1, 2, 3].map(x => x + 1);// bad - 没有返回值, 因为在第一次迭代后 acc 就变成 undefined 了[[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;});// badinbox.filter((msg) => {const { subject, author } = msg;if (subject === 'Mockingbird') {return author === 'Harper Lee';} else {return false;}});// goodinbox.filter((msg) => {const { subject, author } = msg;if (subject === 'Mockingbird') {return author === 'Harper Lee';}return false;});
4.8 如果一个数组有很多行,在数组的 [ 后和 ] 前断行。请看下面示例:
// 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,];
解构
5.1 用对象的解构赋值来获取和使用对象某个或多个属性值。eslint: prefer-destructuring
为什么?解构使您不必为这些属性创建临时引用,并且避免重复引用对象。重复引用对象将造成代码重复、增加阅读次数、提高犯错概率。Destructuring objects also provides a single site of definition of the object structure that is used in the block, rather than requiring reading the entire block to determine what is used.
// 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
const arr = [1, 2, 3, 4];// badconst first = arr[0];const second = arr[1];// goodconst [first, second] = arr;
5.3 多个返回值用对象的解构,而不是数组解构。
为什么?你可以在后期添加新的属性或者变换变量的顺序而不会破坏原有的引用。
// badfunction processInput(input) {// 然后就是见证奇迹的时刻return [left, right, top, bottom];}// 调用者需要想一想返回值的顺序const [left, __, top] = processInput(input);// goodfunction processInput(input) {// oops,奇迹又发生了return { left, right, top, bottom };}// 调用者只需要选择他想用的值就好了const { left, top } = processInput(input);
字符串
6.1 字符串应使用单引号 ‘’ 。eslint: quotes
// badconst name = "Capt. Janeway";// bad - 模板字符串应该包含插入文字或换行const name = `Capt. Janeway`;// goodconst name = 'Capt. Janeway';
6.2 超过 100 个字符的字符串不应该用字符串连接成多行。
为什么?字符串折行增加编写难度且不易被搜索。
// 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 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
6.5 不要使用不必要的转义字符。eslint: no-useless-escape
为什么?反斜线可读性差,因此仅当必要时才使用它。
// badconst foo = '\'this\' \i\s \"quoted\"';// goodconst foo = '\'this\' is "quoted"';//bestconst foo = `my name is '${name}'`;
函数
7.1 使用命名函数表达式而不是函数声明。eslint: func-style
函数表达式: const func = function () {}
函数声明: function func() {}
为什么?函数声明会发生提升,这意味着在一个文件里函数很容易在其被定义之前就被引用了。这样伤害了代码可读性和可维护性。如果你发现一个函数又大又复杂,且这个函数妨碍了这个文件其他部分的理解性,你应当单独把这个函数提取成一个单独的模块。不管这个名字是不是由一个确定的变量推断出来的,别忘了给表达式清晰的命名(这在现代浏览器和类似 babel 编译器中很常见)。这消除了由匿名函数在错误调用栈产生的所有假设。
简单来说就是使用函数声明会发生提升(即在函数被声明之前就可以使用),使用匿名函数会导致难以追踪错误。
// badfunction foo() {// ...}
// bad
const foo = function () {
// …
};
// good
// lexical name distinguished from the variable-referenced invocation(s)
// 函数表达式名和声明的函数名是不一样的
const short = function longUniqueMoreDescriptiveLexicalFoo() {
// …
};
7.2 把立即执行函数包裹在圆括号里。eslint: wrap-iife
立即执行函数:Immediately Invoked Function expression = IIFE。 > 为什么?一个立即调用的函数表达式是一个单元 - 把它和它的调用者(圆括号)包裹起来,使代码读起来更清晰。 > 另外,在模块化世界里,你几乎用不着 IIFE。
// immediately-invoked function expression (IIFE)(function () {console.log('Welcome to the Internet. Please follow me.');}());
7.3 不要在非函数块(if、while 等)内声明函数。把这个函数分配给一个变量。浏览器会允许你这样做,但不同浏览器的解析方式不同,这是一个坏消息。eslint: no-loop-func
7.4 注意:ECMA-262 中对块(block)的定义是: 一系列的语句。但是函数声明不是一个语句, 函数表达式是一个语句。
// badif (currentUser) {function test() {console.log('Nope.');}}// goodlet test;if (currentUser) {test = () => {console.log('Yup.');};}
7.5 不要用 arguments 命名参数。他的优先级高于每个函数作用域自带的 arguments 对象,这会导致函数自带的 arguments 值被覆盖。
// badfunction foo(name, options, arguments) {// ...}// goodfunction foo(name, options, args) {// ...}
7.6 不要使用 arguments,用收集参数语法 … 代替。eslint: prefer-rest-params
为什么?… 明确你想用哪个参数。而且收集参数是真数组,而不是类似数组的 arguments。
// badfunction concatenateAll() {const args = Array.prototype.slice.call(arguments);return args.join('');}// goodfunction concatenateAll(...args) {return args.join('');}
7.7 用默认参数语法而不是在函数里对参数重新赋值。
// really badfunction handleThings(opts) {// 不!我们不该修改 arguments// 第二:如果 opts 的值为 false, 它会被赋值为 {}// 虽然你想这么写,但是这个会带来一些微妙的 bug。opts = opts || {};// ...}// still badfunction handleThings(opts) {if (opts === void 0) {opts = {};}// ...}// goodfunction handleThings(opts = {}) {// ...}
7.8 避免默认参数的副作用。
为什么?他会令人迷惑不解,比如下面这个,a 到底等于几,这个需要想一下。
var b = 1;// badfunction count(a = b++) {console.log(a);}count(); // 1count(); // 2count(3); // 3count(); // 3
7.9 把默认参数赋值放在最后。eslint: default-param-last
// badfunction handleThings(opts = {}, name) {// ...}// goodfunction handleThings(name, opts = {}) {// ...}
7.10 不要用函数构造器创建函数。eslint: no-new-func
为什么?以这种方式创建函数将类似于字符串 eval(),存在漏洞。
// 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 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
为什么?操作参数对象对原始调用者会导致意想不到的副作用。就是不要改参数的数据结构,保留参数原始值和数据结构。
// 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
为什么?参数重新赋值会导致意外行为,尤其是对 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
为什么?这样更清晰,你不必提供上下文(即指定 this 值),而且你不能轻易地用 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 调用或者编写一个包含多个参数的函数的缩进,应该像这个指南里的其他多行代码写法一样——即每行只包含一个参数,每行逗号结尾。
// badfunction foo(bar,baz,quux) {// ...}// good 缩进不要太过分function foo(bar,baz,quux,) {// ...}// badconsole.log(foo,bar,baz);// goodconsole.log(foo,bar,baz,);
箭头函数
8.1 当你一定要用函数表达式(在回调函数里)的时候,使用箭头函数。 eslint: prefer-arrow-callback, arrow-spacing
为什么?箭头函数中的 this 与定义该函数的上下文中的 this 一致,这通常才是你想要的。而且箭头函数是更简洁的语法。
什么时候不用箭头函数:如果你的函数逻辑较复杂,你应该把它单独写入一个命名函数里头。
// 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 如果函数体由一个没有副作用的 表达式 语句组成,删除大括号和 return。否则,使用大括号和  return 语句。 eslint: arrow-parens, arrow-body-style
为什么?语法糖,当多个函数链在一起的时候好读。
// 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 + 1}.`);// 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,}));// 没有明显的 return 语句,可能存在副作用。function foo(callback) {const val = callback();if (val === true) {// 当 callback 返回 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 在箭头函数参数两头,总是使用小括号包裹住参数,这样做使代码更清晰且一致. eslint: arrow-parens
为什么?当你想要添加或删除参数时能比较省事。
// bad[1, 2, 3].map(x => x * x);// good[1, 2, 3].map((x) => x * x);// bad[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!`));// 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
// badconst itemHeight = (item) => item.height <= 256 ? item.largeSize : item.smallSize;// badconst itemHeight = (item) => item.height >= 256 ? item.largeSize : item.smallSize;// goodconst itemHeight = (item) => (item.height <= 256 ? item.largeSize : item.smallSize);// goodconst itemHeight = (item) => {const { height, largeSize, smallSize } = item;return height <= 256 ? largeSize : smallSize;};
8.6 使箭头函数体有一个清晰的返回。 eslint: implicit-arrow-linebreak
// 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 来实现链式调用。
// badJedi.prototype.jump = function () {this.jumping = true;return true;};Jedi.prototype.setHeight = function (height) {this.height = height;};const luke = new Jedi();luke.jump(); // => trueluke.setHeight(20); // => undefined// goodclass Jedi {jump() {this.jumping = true;return this;}setHeight(height) {this.height = height;return this;}}const luke = new Jedi();luke.jump().setHeight(20);
9.4 自己写 toString() 方法是可以的,但需要保证它可以正常工作且没有副作用。
class Jedi {constructor(options = {}) {this.name = options.name || 'no name';}getName() {return this.name;}toString() {return `Jedi - ${this.getName()}`;}}
9.5 如果没有特别定义,类有默认的构造方法。一个空的构造函数或只是代表父类的构造函数是不需要写的。 eslint: no-useless-constructor
// badclass Jedi {constructor() {}getName() {return this.name;}}// badclass Rey extends Jedi {// 这种构造函数是不需要写的constructor(...args) {super(...args);}}// goodclass Rey extends Jedi {constructor(...args) {super(...args);this.name = 'Rey';}}
9.6 避免重复定义类成员。eslint: no-dupe-class-members
为什么?重复定义类成员只会使用最后一个被定义的 —— 重复本身也是一个 bug.
// badclass Foo {bar() { return 1; }bar() { return 2; }}// goodclass Foo {bar() { return 1; }}// goodclass Foo {bar() { return 2; }}
9.7 除非外部库或框架需要使用特定的非静态方法,否则类方法应该使用 this 或被写成静态方法。    作为一个实例方法表明它应该根据实例的属性有不同的行为。eslint: class-methods-use-this
// badclass Foo {bar() {console.log('bar');}}// good - this 被使用了class Foo {bar() {console.log(this.bar);}}// good - constructor 不一定要使用 thisclass Foo {constructor() {// ...}}// good - 静态方法不需要使用 thisclass Foo {static bar() {console.log('bar');}}
模块
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 不要用 import 通配符, 即 * 这种方式。
为什么?这确保你有单个默认的导出。
// badimport * as AirbnbStyleGuide from './AirbnbStyleGuide';// goodimport AirbnbStyleGuide from './AirbnbStyleGuide';
10.3 不要直接从 import 中直接 export。
为什么?虽然只写一行很简洁,但是使用明确 import 和明确的 export 来保证一致性。
// bad// filename es6.jsexport { es6 as default } from './AirbnbStyleGuide';// good// filename es6.jsimport { es6 } from './AirbnbStyleGuide';export default es6;
10.4 一个路径只 import 一次。eslint: 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
为什么?变化通常都是需要避免,特别是当你要输出可变的绑定。虽然在某些场景下可能需要这种技术,但总的来说应该导出常量。
// badlet foo = 3;export { foo }// goodconst foo = 3;export { foo }
10.6 在一个单一导出模块里,用 export default 更好。eslint: import/prefer-default-export
为什么?鼓励使用更多文件,每个文件只导出一次,这样可读性和可维护性更好。
// badexport function foo() {}
// good
export default function foo() {}
10.7 把 import 放在其他所有语句之前。eslint: import/first
为什么?因为 import 会被提升到代码最前面运行,因此将他们放在最前面以防止发生意外行为。
// badimport foo from 'foo';foo.init();import bar from 'bar';// goodimport foo from 'foo';import bar from 'bar';foo.init();
10.8 多行 import 应该缩进,就像多行数组和对象字面量一样。
为什么?花括号与样式指南中每个其他花括号块遵循相同的缩进规则,逗号也是。
// badimport {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';// goodimport {longNameA,longNameB,longNameC,longNameD,longNameE,} from 'path';
10.9 在 import 语句里不允许 Webpack loader 语法。eslint: import/no-webpack-loader-syntax
为什么?一旦用 Webpack 语法在 import 里会把代码耦合到模块绑定器。最好是在 webpack.config.js 里写 webpack loader 语法
// 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 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
为什么?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
const luke = {jedi: true,age: 28,};// badconst isJedi = luke['jedi'];// goodconst isJedi = luke.jedi;
12.2 当使用变量获取属性时用方括号 []。
const luke = {jedi: true,age: 28,};function getProp(prop) {return luke[prop];}const isJedi = getProp('jedi');
12.3 做幂运算时用幂操作符 ** 。eslint: no-restricted-properties.
// badconst binary = Math.pow(2, 10);// goodconst binary = 2 ** 10;
变量
13.1  使用 const 或 let 声明变量。不这样做会导致全局变量。我们想要避免污染全局命名空间。eslint: no-undef prefer-const
// badsuperPower = new SuperPower();// goodconst superPower = new SuperPower();
13.2 为每个变量声明都用一个 const 或 let 。eslint: one-var
为什么?这种方式很容易去声明新的变量,你不用去考虑把 ; 调换成 ,,或者引入一个只有标点的不同的变化(译者注:这里说的应该是在 Git 提交代码时显示的变化)。这种做法也可以是你在调试的时候单步每个声明语句,而不是一下跳过所有声明。
// badconst items = getItems(),goSportsTeam = true,dragonball = 'z';// bad// (与前面的比较,找一找错误)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
为什么?链式声明变量会创建隐式全局变量。
// bad(function example() {// JavaScript 将这一段解释为// let a = ( b = ( c = 1 ) );// let 只对变量 a 起作用; 变量 b 和 c 都变成了全局变量let a = b = c = 1;}());console.log(a); // undefinedconsole.log(b); // 1console.log(c); // 1// good(function example() {let a = 1;let b = a;let c = a;}());console.log(a); // undefinedconsole.log(b); // undefinedconsole.log(c); // undefined// `const` 也是如此
13.6 不要使用一元自增自减运算符(++, —). eslint 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,那就用小括号把这个值包起来再换行。eslint operator-linebreak.
为什么?在 = 附近换行容易混淆这个赋值语句。
// badconst foo =superLongLongLongLongLongLongLongLongFunctionName();// badconst foo= 'superLongLongLongLongLongLongLongLongString';// goodconst foo = (superLongLongLongLongLongLongLongLongFunctionName());// goodconst foo = 'superLongLongLongLongLongLongLongLongString';
13.8 不允许有未使用的变量。eslint: no-unused-vars
为什么?一个声明了但未使用的变量更像是由于重构未完成产生的错误。这种在代码中出现的变量会使阅读者迷惑。
// badvar some_unused_var = 42;// 写了没用var y = 10;y = 5;// 变量改了自己的值,也没有用这个变量var z = 0;z = z + 1;// 参数定义了但未使用function getX(x, y) {return x;}// goodfunction getXPlusY(x, y) {return x + y;}var x = 1;var y = a + 2;alert(getXPlusY(x, y));// 'type' 即使没有使用也可以可以被忽略, 因为这个有一个 rest 取值的属性。// 这是从对象中抽取一个忽略特殊字段的对象的一种形式var { type, ...coords } = data;// 'coords' 现在就是一个没有 'type' 属性的 'data' 对象
提升
14.1 var 声明会被提前到离他最近的作用域的最前面,但是它的赋值语句并没有提前。const 和 let 被赋予了新的概念 暂时性死区。 重要的是要知道为什么 typeof 不再安全.
// 我们知道这个不会工作,假设没有定义全局的 notDefinedfunction example() {console.log(notDefined); // => throws a ReferenceError}// 在你引用的地方之后声明一个变量,他会正常输出是因为变量提升。// 注意: declaredButNotAssigned 的值 true 没有被提升。function example() {console.log(declaredButNotAssigned); // => undefinedvar declaredButNotAssigned = true;}// 解释器把变量声明提升到作用域最前面,// 可以重写成如下例子, 二者意义相同。function example() {let declaredButNotAssigned;console.log(declaredButNotAssigned); // => undefineddeclaredButNotAssigned = true;}// 用 const,let就不一样了。function example() {console.log(declaredButNotAssigned); // => throws a ReferenceErrorconsole.log(typeof declaredButNotAssigned); // => throws a ReferenceErrorconst declaredButNotAssigned = true;}
14.2 匿名函数表达式和 var 情况相同。
function example() {console.log(anonymous); // => undefinedanonymous(); // => TypeError anonymous is not a function// 译者注,不管后面是函数、数字还是字符串,都是一样的,总结就是实际代码中最好不要用 var。var anonymous = function () {console.log('anonymous function expression');};}
14.3 已命名函数表达式提升他的变量名,不是函数名或函数体。
function example() {console.log(named); // => undefinednamed(); // => TypeError named is not a functionsuperPower(); // => ReferenceError superPower is not definedvar named = function superPower() {console.log('Flying');};}// 函数名和变量名一样是也如此。function example() {console.log(named); // => undefinednamed(); // => TypeError named is not a functionvar named = function named() {console.log('named');};}
14.4 函数声明则提升了函数名和函数体。
function example() {superPower(); // => Flyingfunction superPower() {console.log('Flying');}}
比较运算符与相等
15.1 用 === 和 !== 而不是 == 和 !=. eslint: eqeqeq
15.2 条件语句如 if 语句使用强制 ToBoolean 抽象方法来计算它们的表达式,并且始终遵循以下简单规则:
Objects  计算成 true
Undefined 计算成 false
Null 计算成 false
Booleans  计算成 the value of the boolean
Numbers
+0, -0, or NaN 计算成 false
其他 true
Strings
‘’ 计算成 false
其他 true
if ([0] && []) {// true// 数组(即使是空数组)是对象,对象会计算成 true}
15.3 布尔值要用缩写,而字符串和数字要明确使用比较操作符。
// badif (isValid === true) {// ...}// goodif (isValid) {// ...}// badif (name) {// ...}// goodif (name !== '') {// ...}// badif (collection.length) {// ...}// goodif (collection.length > 0) {// ...}
15.4 更多信息请见 Angus Croll 的 Truth Equality and JavaScript。
15.5 在 case 和 default 分句里用大括号创建一块包含词法声明的区域(例如:let、const、function 和 class)。eslint rules: no-case-declarations.
为什么?词法声明在整个 switch 的代码块里都可见,但是只有当其被分配后才会初始化,仅当这个 case 被执行时才被初始化。当多个 case 分句试图定义同一个对象时就会出现问题。
// 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 rules: no-nested-ternary
// bad
const foo = maybe1 > maybe2
? “bar”
: value1 > value2 ? “baz” : null;
// better
const maybeNull = value1 > value2 ? ‘baz’ : null;
const foo = maybe1 > maybe2? 'bar': maybeNull;// best
const maybeNull = value1 > value2 ? ‘baz’ : null;
const foo = maybe1 > maybe2 ? 'bar' : maybeNull;
15.7 避免不必要的三元表达式。eslint rules: no-unneeded-ternary
// bad
const foo = a ? a : b;
const bar = c ? true : false;
const baz = c ? false : true;
// good
const foo = a || b;
const bar = !!c;
const baz = !c;
15.8 用圆括号来组合操作符。 只有当标准的算术运算符(+, -, *, 和 /), 并且它们的优先级显而易见时,才可以不用圆括号括起来。eslint: no-mixed-operators
为什么?这提高了可读性,并且明确了开发者的意图。
// 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
// badif (test)return false;// goodif (test) return false;// goodif (test) {return false;}// badfunction foo() { return false; }// goodfunction bar() {return false;}
16.2 if 表达式的 else和 if 的右大括号在一行。eslint: brace-style
// badif (test) {thing1();thing2();}else {thing3();}// goodif (test) {thing1();thing2();} else {thing3();}
16.3 如果 if 语句中总是需要用 return 返回,那后续的 else 就不需要写了。 if 块中包含 return, 它后面的 else if 块中也包含了 return, 这个时候就可以把 return 分到多个 if 语句块中。 eslint: no-else-return
// badfunction foo() {if (x) {return x;} else {return y;}}// badfunction cats() {if (x) {return x;} else if (y) {return y;}}// badfunction dogs() {if (x) {return x;} else {if (y) {return y;}}}// goodfunction foo() {if (x) {return x;}return y;}// goodfunction cats() {if (x) {return x;}if (y) {return y;}}// goodfunction dogs(x) {if (x) {if (z) {return y;}} else {return z;}}
控制语句
17.1 当你的控制语句(if, while 等)太长或者超过最大长度限制的时候,把每一个(组)判断条件放在单独一行里。逻辑操作符放在行首。
为什么?把逻辑操作符放在行首是让操作符的对齐方式和链式函数保持一致。这提高了可读性,也让复杂逻辑更清晰。
// 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 不要用选择操作符代替控制语句。
// bad!isRunning && startRunning();// goodif (!isRunning) {startRunning();}
注释
18.1 多行注释用 /* … /。
// 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 单行注释用 //,将单行注释放在被注释区域上面。如果注释不是在第一行,那么注释前面就空一行。
// badconst active = true; // is current tab// good// is current tabconst active = true;// badfunction getType() {console.log('fetching type...');// set the default type to 'no type'const type = this._type || 'no type';return type;}// goodfunction getType() {console.log('fetching type...');// set the default type to 'no type'const type = this._type || 'no type';return type;}// also goodfunction getType() {// set the default type to 'no type'const type = this._type || 'no type';return type;}
18.3 所有注释开头空一格,方便阅读。eslint: spaced-comment
// 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: shouldn't use a global heretotal = 0;}}
18.6 用 // TODO: 去注释问题的解决方案。
class Calculator extends Abacus {constructor() {super();// TODO: total should be configurable by an options paramthis.total = 0;}}
空格
19.1 一个缩进使用两个空格。eslint: indent
// badfunction foo() {∙∙∙∙const name;}// badfunction bar() {∙const name;}// goodfunction baz() {∙∙const name;}
19.2 在大括号前空一格。eslint: space-before-blocks
// 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
// badif(isJedi) {fight ();}// goodif (isJedi) {fight();}// badfunction fight () {console.log ('Swooosh!');}// goodfunction fight() {console.log('Swooosh!');}
19.4 用空格来隔开运算符。eslint: space-infix-ops
// badconst x=y+5;// goodconst x = y + 5;
19.5 文件结尾空一行。eslint: eol-last
// badimport { es6 } from './AirbnbStyleGuide';// ...export default es6;// badimport { es6 } from './AirbnbStyleGuide';// ...export default es6;↵↵// goodimport { es6 } from './AirbnbStyleGuide';// ...export default es6;↵
19.6 当出现长的方法链式调用时(>2个)用缩进。用点开头强调该行是一个方法调用,而不是一个新的语句。eslint: newline-per-chained-call no-whitespace-before-property
// 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();// badconst 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);// goodconst 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);// goodconst leds = stage.selectAll('.led').data(data);
19.7 在一个代码块后下一条语句前空一行。
// 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
// badfunction bar() {console.log(foo);}// also badif (baz) {console.log(qux);} else {console.log(foo);}// goodfunction bar() {console.log(foo);}// goodif (baz) {console.log(qux);} else {console.log(foo);}
19.9 不要在代码之间使用多个空白行填充。eslint: no-multiple-empty-lines
// bad
class Person {
constructor(fullName, email, birthday) {
this.fullName = fullName;
this.email = email;this.setAge(birthday);
}
setAge(birthday) {
const today = new Date();
const age = this.getAge(today, birthday);this.age = age;
}
getAge(today, birthday) {
// ..
}
}
// good
class Person {
constructor(fullName, email, birthday) {
this.fullName = fullName;
this.email = email;
this.setAge(birthday);
}
setAge(birthday) {
const today = new Date();
const age = getAge(today, birthday);
this.age = age;
}
getAge(today, birthday) {
// ..
}
}
19.10 圆括号里不要加空格。eslint: space-in-parens
// badfunction bar( foo ) {return foo;}// goodfunction bar(foo) {return foo;}// badif ( foo ) {console.log(foo);}// goodif (foo) {console.log(foo);}
19.11 方括号里不要加空格。 eslint: array-bracket-spacing
// badconst foo = [ 1, 2, 3 ];console.log(foo[ 0 ]);// good,逗号分隔符后还是要空格的。const foo = [1, 2, 3];console.log(foo[0]);
19.12 花括号里加空格 。eslint: object-curly-spacing
// badconst foo = {clark: 'kent'};// goodconst foo = { clark: 'kent' };
19.13 避免一行代码超过100个字符(包含空格)。注意:对于 上面,长字符串不受此规则限制,不应换行。 eslint: max-len
为什么?这样确保可读性和可维护性。
// 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.14 作为语句的花括号内也要加空格 —— { 后和 } 前都需要空格。 eslint: block-spacing
// badfunction foo() {return true;}if (foo) { bar = 0;}// goodfunction foo() { return true; }if (foo) { bar = 0; }
19.15 , 前不要空格, , 后需要空格。 eslint: comma-spacing
// badvar foo = 1,bar = 2;var arr = [1 , 2];// goodvar foo = 1, bar = 2;var arr = [1, 2];
19.16 计算属性内要空格。参考上述花括号和中括号的规则。  eslint: computed-property-spacing
译者注:原文可能有误,说明和代码不一致,以代码为准。// badobj[foo ]obj[ 'foo']
var x = {[ b ]: a}
obj[foo[ bar ]]
// goodobj[foo]obj['foo']var x = { [b]: a }obj[foo[bar]]
19.17 调用函数时,函数名和小括号之间不要空格。 eslint: func-call-spacing
// badfunc ();func();// goodfunc();
19.18 在对象的字面量属性中, key  和 value 之间要有空格。 eslint: key-spacing
// badvar obj = { "foo" : 42 };var obj2 = { "foo":42 };// goodvar obj = { "foo": 42 };
19.19 行末不要空格。 eslint: no-trailing-spaces
19.20 避免出现多个空行。 在文件末尾只允许空一行。避免在文件开始处出现空行。eslint: no-multiple-empty-lines
// bad - multiple empty lines
var x = 1;
var y = 2;
// bad - 2+ newlines at end of file
var x = 1;
var y = 2;
// bad - 1+ newline(s) at beginning of file
var x = 1;
var y = 2;
// good
var x = 1;
var y = 2;
逗号
20.1 不要前置逗号。eslint: comma-style
// badconst story = [once, upon, aTime];// goodconst story = [once,upon,aTime,];// badconst hero = {firstName: 'Ada', lastName: 'Lovelace', birthYear: 1815, superPower: 'computers'};// goodconst hero = {firstName: 'Ada',lastName: 'Lovelace',birthYear: 1815,superPower: 'computers',};
20.2 额外结尾逗号: 要 eslint: comma-dangle
为什么?这使 git diffs 更简洁。此外,像Babel这样的转换器会删除转换代码中的额外的逗号,这意味着你不必担心旧版浏览器中的 结尾逗号问题。
// bad - 没有结尾逗号的 git diffconst hero = {firstName: 'Florence',- lastName: 'Nightingale'+ lastName: 'Nightingale',+ inventorOf: ['coxcomb chart', 'modern nursing']};// good - 有结尾逗号的 git diffconst 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 (注意,逗号不应出现在使用了 ... 操作符后的参数后面)function createHero(firstName,lastName,inventorOf,...heroArgs) {// does nothing}// badcreateHero(firstName,lastName,inventorOf);// goodcreateHero(firstName,lastName,inventorOf,);// good (注意,逗号不应出现在使用了 ... 操作符后的参数后面)createHero(firstName,lastName,inventorOf,...heroArgs)
分号
21.1 要分号! eslint: semi
为什么?当 JavaScript 遇到没有分号结尾的一行,它会执行 自动插入分号 这一规则来决定行末是否加分号。如果 JavaScript 在你的断行里错误的插入了分号,就会出现一些古怪的行为。当新的功能加到JavaScript 里后, 这些规则会变得更复杂难懂。清晰的结束语句,并通过配置代码检查去检查没有带分号的地方可以帮助你防止这种错误。
// bad - 抛出异常
const luke = {}
const leia = {}
[luke, leia].forEach((jedi) => jedi.father = ‘vader’)
// bad - 抛出异常
const reaction = “No! That’s impossible!”
(async function meanwhileOnTheFalcon() {
// 处理 leia, lando, chewie, r2, c3p0
// …
}())
// bad - 将返回 undefined 而不是下一行的值。由于 ASI,当 return单独出现在一行时,这种情况会一直出现。
function foo() {
return
‘search your feelings, you know it to be foo’
}
// good
const luke = {};
const leia = {};
[luke, leia].forEach((jedi) => {
jedi.father = ‘vader’;
});
// good
const reaction = “No! That’s impossible!”;
(async function meanwhileOnTheFalcon() {
// handle leia, lando, chewie, r2, c3p0
// …
}());
// good
function foo() {
return ‘search your feelings, you know it to be foo’;
}
更多.
类型转换与强制转换
22.1 在语句开始执行强制类型转换。
22.2  字符串: eslint: no-new-wrappers
// => this.reviewScore = 9;// badconst totalScore = new String(this.reviewScore); // typeof totalScore is "object" not "string"// badconst totalScore = this.reviewScore + ''; // 将会执行 this.reviewScore.valueOf()// badconst totalScore = this.reviewScore.toString(); // 不保证返回 string// goodconst totalScore = String(this.reviewScore);
22.3 数字: 用 Number 做类型转换,parseInt 转换 string 应总是带上基数。 eslint: radix
为什么?函数 parseInt  会根据指定的基数将字符串转换为数字。字符串开头的空白字符将会被忽略,如果参数基数(第二个参数)为 undefined 或者 0 ,除非字符串开头为 0x 或 0X(十六进制),会默认假设为 10。这个差异来自 ECMAScript 3,它不鼓励(但是允许)解释八进制。在 2013 年之前,一些实现不兼容这种行为。因为我们需要支持旧浏览器,所以应当始终指定进制。    >
译者注:翻译的可能不是很好,总之使用 parseInt() 时始终指定进制数(第二个参数)就可以了。
const inputValue = ‘4’;
// bad
const val = new Number(inputValue);
// bad
const val = +inputValue;
// bad
const val = inputValue >> 0;
// bad
const val = parseInt(inputValue);
// goodconst val = Number(inputValue);// goodconst val = parseInt(inputValue, 10);
22.4 请在注释中解释为什么要用移位运算和你在做什么。无论你做什么狂野的事,比如由于 parseInt 是你的性能瓶颈导致你一定要用移位运算。说明这个是因为 性能原因。
// good/*** parseInt 是代码运行慢的原因* 用 Bitshifting 将字符串转成数字使代码运行效率大幅提升*/const val = inputValue >> 0;
22.5 注意: 用移位运算要小心。数字是用 64-位表示的,但移位运算常常返回的是32为整形source)。移位运算对大于 32 位的整数会导致意外行为。Discussion. 最大的 32 位整数是 2,147,483,647:
2147483647 >> 0 //=> 21474836472147483648 >> 0 //=> -21474836482147483649 >> 0 //=> -2147483647
22.6 布尔:
const age = 0;// badconst hasAge = new Boolean(age);// goodconst hasAge = Boolean(age);// bestconst hasAge = !!age;
命名规范
23.1 避免用一个字母命名,让你的命名有意义。eslint: id-length
// badfunction q() {// ...}// goodfunction query() {// ...}
23.2 用小驼峰命名法来命名你的对象、函数、实例。eslint: camelcase
// badconst OBJEcttsssss = {};const this_is_my_object = {};function c() {}// goodconst thisIsMyObject = {};function thisIsMyFunction() {}
23.3 用大驼峰命名法来命名类。eslint: new-cap
// 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
为什么?JavaScript 没有私有属性或私有方法的概念。尽管前置下划线通常的概念上意味着私有,事实上,这些属性是完全公有的,因此这部分也是你的 API 的内容。这一概念可能会导致开发者误以为更改这个不会导致崩溃或者不需要测试。如果你想要什么东西变成私有,那就不要让它在这里出现。
// badthis.__firstName__ = 'Panda';this.firstName_ = 'Panda';this._firstName = 'Panda';// goodthis.firstName = 'Panda';
23.5 不要保存引用 this,用箭头函数或 函数绑定——Function#bind。
// badfunction foo() {const self = this;return function () {console.log(self);};}// badfunction foo() {const that = this;return function () {console.log(that);};}// goodfunction foo() {return () => {console.log(this);};}
23.6 export default 导出模块A,则这个文件名也叫 A.*, import 时候的参数也叫 A。 大小写完全一致。
// 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 当你 export-default 一个函数时,函数名用小驼峰,文件名需要和函数名一致。
function makeStyleGuide() {// ...}export default makeStyleGuide;
23.8 当你 export 一个结构体/类/单例/函数库/对象 时用大驼峰。
const 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 你可以用全大写字母设置静态变量,他需要满足三个条件。
导出变量;
是 const 定义的, 保证不能被改变;
这个变量是可信的,他的子属性都是不能被改变的。
为什么?这是一个附加工具,帮助开发者去辨识一个变量是不是不可变的。UPPERCASE_VARIABLES 能让开发者知道他能确信这个变量(以及他的属性)是不会变的。
对于所有的 const 变量呢? —— 这个是不必要的。大写变量不应该在同一个文件里定义并使用, 它只能用来作为导出变量。
那导出的对象呢? —— 大写变量处在 export 的最高级(例如:EXPORTED_OBJECT.key) 并且他包含的所有子属性都是不可变的。(译者注:即导出的变量是全大写的,但他的属性不用大写)
// 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 - 不必要的大写键,没有增加任何语义export const MAPPING = {KEY: 'value'
};
// goodexport const MAPPING = {key: 'value'};
Get-Set 访问器
24.1 不需要使用属性的访问器函数。
24.2 不要使用 JavaScript 的 getters/setters,因为他们会产生副作用,并且难以测试、维护和理解。相反的,你可以用 getVal() 和 setVal(‘hello’) 去创造你自己的访问器函数。
// badclass Dragon {get age() {// ...}set age(value) {// ...}}// goodclass Dragon {getAge() {// ...}setAge(value) {// ...}}
24.3 如果属性/方法是 boolean, 用 isVal() 或 hasVal()。
// badif (!dragon.age()) {return false;}// goodif (!dragon.hasAge()) {return false;}
24.4 用 get() 和 set() 函数是可以的,但是要一起用。
class 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 还是像 Backbone 这样有很多属性的事件)。这使得后续的贡献者(程序员)向这个事件添加更多的数据时不用去找或者更新每个处理器。例如:
// bad$(this).trigger('listingUpdated', listing.id);// ...$(this).on('listingUpdated', (e, listingID) => {// do something with listingID});prefer:// good$(this).trigger('listingUpdated', { listingID: listing.id });// ...$(this).on('listingUpdated', (e, data) => {// do something with data.listingID});
