Airbnb JavaScript 编码风格指南(2018年最新版)
Airbnb JS规范原文: https://github.com/airbnb/javascript
类型
1.基本类型:直接存取
- string
- number
- boolean
- null
- undefined
- symbol
1
2
3
4
5
6const foo = 1;
let bar = foo;
bar = 9;
console.log(foo, bar); // => 1, 9
symbol 类型不能完全polyfilled,所以请谨慎使用
2.复杂类型: 通过引用的方式存取
- object
- array
- function
1
2
3
4
5
6const foo = [1, 2];
const bar = foo;
bar[0] = 9;
console.log(foo[0], bar[0]); // => 9, 9
引用
1.使用const申明引用类型,避免使用var。eslint 设置:prefer-const,no-const-assign
为什么?这能确保你无法对引用重新赋值,也不会导致出现 bug 或难以理解。
1 | // bad |
2.如果必须对引用类型重新赋值,使用let而非var。eslint设置:no-var
为什么?相比于var函数作用域,let块级作用域更容易理解
1 | // bad |
3.注意let和const都是块级作用域1
2
3
4
5
6
7// const and let only exist in the blocks they are defined in.
{
let a = 1;
const b = 1;
}
console.log(a); // ReferenceError
console.log(b); // ReferenceError
对象
1.使用字面值创建对象。eslint: no-new-object1
2
3
4
5// bad
const item = new Object();
// good
const item = {};
2.创建对象的动态属性时,使用计算属性
为什么?这样可以在一个地方定义对象所有的属性
1 | function getKey(k) { |
3.使用对象方法的简写形式。 eslint: object-shorthand
为什么?方法定义简洁清晰
1 | // bad |
4.使用属性值简写形式
为什么?书写更加简洁,更有描述性。
1 | const lukeSkywalker = 'Luke Skywalker'; |
5.对象声明时分类简写和非简写的属性名。
为什么?更清晰的了解哪些属性是简写的。
1 | const anakinSkywalker = 'Anakin Skywalker'; |
6.只有对那些不合法的属性名标识符添加引号。
为什么?对象属性更直观,可读性强。能够代码高亮显示,同时对于大多数的js引擎更容易优化代码。
1 | // bad |
7.不要直接使用Object.prototype上的方法,例如hasOwnProperty, propertyIsEnumerable, 和 isPrototypeOf。
为什么?这些方法可能受对象的其他属性影响。例如{ hasOwnProperty: false } 或者 对象可能是null(Object.create(null))
1 | // bad |
8.浅拷贝对象时推荐使用对象展开操作(object spread operator)而不是Object.assign。使用对象剩余操作符(object rest operator)获取对象中剩余的属性。
为什么?Object.assign使用不当会修改原对象
1 | // very bad |
数组
1.使用字面量声明数组。1
2
3
4
5// bad
const items = new Array();
// good
const items = [];
2.向数组添加元素时,使用Arrary#push替代直接赋值。1
2
3
4
5
6
7const someStack = [];
// bad
someStack[someStack.length] = 'abracadabra';
// good
someStack.push('abracadabra');
3.使用数组展开操作符…拷贝数组1
2
3
4
5
6
7
8
9
10
11// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i += 1) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
4.将类数组对象(array-like)转换成数组时,使用…而不是Array.from1
2
3
4
5
6
7const foo = document.querySelectorAll('.foo');
// good
const nodes = Array.from(foo);
// best
const nodes = [...foo];
5.当需要对可遍历对象进行map操作时,使用Array.from而不是展开操作符…,避免新建一个临时数组。1
2
3
4
5// bad
const baz = [...foo].map(bar);
// good
const baz = Array.from(foo, bar);
6.数组方法回调需要有返回值。如果函数体比较简单,可以直接用表达式,省略return语句。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map(x => x + 1);
// bad - no returned value means `memo` becomes undefined after the first iteration
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
const flatten = memo.concat(item);
memo[index] = flatten;
});
// good
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
const flatten = memo.concat(item);
memo[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;
});
7.如果数组有多行,请在打开和关闭数组括号之前使用换行符(特别是数组对象数据)
为什么? 更具有可读性
1 | // bad |
解构
1.访问和使用对象的多个属性时用对象解构操作。
为什么?解构可以避免为这些属性创建临时引用。
1 | // bad |
2.使用数组解构1
2
3
4
5
6
7
8const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
3.使用对象解构来实现多个返回值,而不是数组解构。
为什么?你可以随时为返回值新增属性而不用关心属性的顺序。
1 | // bad |
字符串
1.字符串使用单引号。1
2
3
4
5
6
7
8// bad
const name = "Capt. Janeway";
// bad - 当需要插值或者换行时才使用模板文字
const name = `Capt. Janeway`;
// good
const name = 'Capt. Janeway';
2.不超过100个字符的字符串不应该使用连接符或者换行书写。
为什么?换行的字符串不好阅读,并且不方便搜索代码。
1 | // bad |
3.以编程方式构建字符串时,使用模板字符串而不是连接符。
为什么?模板字符串更为简洁,更具可读性。
1 | // bad |
4.永远不要在字符串上使用eval()方法,它有太多的问题。
5.不要过多的转义字符串。
为什么?反斜杠影响代码可读性,只有在必要的时候才使用。
1 | // bad |
函数
1.使用命名函数表达式而不是函数声明。
为什么?函数声明会被提前。这意味着很可能在函数定义前引用该函数,但是不会报错。这不利于代码的可读性和可维护性。如果你发现一个函数定义的很大很复杂,以至于妨碍了了解文件中的其他内容,那么是时候把这个函数提取到自己的模块中去了!不要忘记显示指定表达式的名称,尽管它能从变量名中被推断出来(现代浏览器或者编译器(如Babel)支持)。这能让错误的调用栈更清晰。(讨论)
1 | // bad |
1 | // Is it worse |
第一个函数没有.name属性,在debugging过程中,它会是一个匿名函数。第二个函数有名字为sum,你可以检索到它,调试过程中能够快速定位。
使用banel 和babel-preset-env配置,const foo = () => {}会转换成var foo = function foo () {},并且从Node v6开始,const foo = () => {}中的foo 也有.name。所以它不再是匿名函数。(函数名字推断)
2.用圆括号包裹立即执行函数表达式(IIFE)。
为什么? 立即执行函数表达式是单一执行单元-使用圆括号包裹调用,简洁明了的表示了这一点。请注意,在通用的模块中,你几乎用不到IIFE。
1 | // immediately-invoked function expression (IIFE) |
3.永远不要在一个非函数代码块(if、while 等)中声明一个函数,把那个函数赋给一个变量。浏览器允许你这么做,但它们的解析表现不一致。
4.注意:ECMA-262把block定义为一组语句。但是函数声明不是语句。1
2
3
4
5
6
7
8
9
10
11
12
13
14// bad
if (currentUser) {
function test() {
console.log('Nope.');
}
}
// good
let test;
if (currentUser) {
test = () => {
console.log('Yup.');
};
}
5.永远不要把参数命名为arguments。这将取代原来函数作用域内的 arguments对象。1
2
3
4
5
6
7
8
9// bad
function foo(name, options, arguments) {
// ...
}
// good
function foo(name, options, args) {
// ...
}
6.不要使用arguments。可以选择 rest 语法 … 替代。
为什么?使用 … 能明确你要传入的参数。另外 rest 参数是一个真正的数组,而 arguments 是一个类数组。
1 | // bad |
7.使用函数默认参数指定默认值,而不是用一个可变的函数参数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// really bad
function handleThings(opts) {
// 不!我们不应该改变函数参数
// 更糟糕的是: 如果 opts 是 falsy (为''或者是false), 它仍然会被赋值为对象,但是这可能会引发bug
opts = opts || {};
// ...
}
// still bad
function handleThings(opts) {
if (opts === void 0) {
opts = {};
}
// ...
}
// good
function handleThings(opts = {}) {
// ...
}
8.1使用默认参数避免副作用。
为什么?这样的写法会让人困惑。
1 | var b = 1; |
参数默认值放在函数参数列表的最后。
1 | // bad |
8.2不要使用Function构造器创建函数。
为什么?通过这种方式创建的函数和使用eval()类似,会带来不确定的问题
1 | // bad |
8.3函数名两边留白。
为什么?保持代码一致性,当你添加或者删除名字时不需要额外增减空格。
1 | // bad |
8.4不要修改参数。
为什么?操作参数对象会在原始调用方中导致不可预知的变量副作用。
1 | // bad |
8.5不要给参数赋值。
为什么?重新分配参数可能会导致意外的行为,特别是在访问参数对象时。 它也可能导致优化问题,特别是在V8中。
1 | // bad |
8.6使用展开操作符…调用可变参数函数。
为什么?它更简洁,你不需要提供上下文,并且组合使用new和apply不容易。
1 | // bad |
8.7带有多行函数签名或调用的函数应该像本指南中的其他多行列表一样缩进:每行中包含一项,最后一个项目带有逗号。
1 | // bad |
箭头函数
1.当你必须要使用匿名函数(如在传递内联回调时),请使用箭头函数。
为什么?因为箭头函数创造了新的一个 this 执行环境,通常情况下都能满足你的需求,而且这样的写法更为简洁。(参考 Arrow functions - JavaScript | MDN )
为什么不?如果你有一个相当复杂的函数,你或许可以把逻辑部分转移到一个函数声明上。
1 | // bad |
2.如果一个函数适合用一行写出并且只有一个参数,那就把花括号、圆括号和 return 都省略掉。如果不是,那就不要省略。
为什么?这是一个很好用的语法糖。在链式调用中可读性很高。
1 | // bad |
3.如果表达式过长需要多行表示,请将其包含在括号中,增加可读性。
为什么?它能清除的标识函数的开始和结束位置。
1 | // bad |
4.如果函数只有一个参数并且函数体没有使用花括号,那就省略括号。否则,为了保持清晰一致性,总在参数周围加上括号。
为什么? 不那么混乱,可读性强。
1 | // bad |
5.避免箭头函数语法(=>)和比较运算符(<=,=>)一起使用时带来的困惑。1
2
3
4
5
6
7
8
9
10
11
12
13
14// 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;
};
类 & 构造函数
1.总是使用class。避免直接操作prototype。
为什么?class语法更简洁更易于理解。
1 | // bad |
2.使用extends继承。
为什么? 因为 extends 是一个内建的原型继承方法并且不会破坏 instanceof。
1 | // bad |
3.方法可以返回 this 来帮助链式调用。
1 | // bad |
4.可以写一个自定义的 toString() 方法,但要确保它能正常运行并且不会引起副作用。1
2
3
4
5
6
7
8
9
10
11
12
13class Jedi {
constructor(options = {}) {
this.name = options.name || 'no name';
}
getName() {
return this.name;
}
toString() {
return `Jedi - ${this.getName()}`;
}
}
5.类有默认构造器。一个空的构造函数或者只是重载父类构造函数是不必要的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 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';
}
}
6.避免重复的类成员。
为什么?重复的类成员声明中只有最后一个生效-重复的声明肯定是一个错误。
1 | // bad |
模块
1.总是使用模组 (import/export) 而不是其他非标准模块系统。你可以编译为你喜欢的模块系统。
为什么?模块是未来,让我们开始迈向未来吧。
1 | // bad |
2.不要使用通配符 import
为什么?这样确保只有一个默认的export
1 | // bad |
3.不要直接从import中export
为什么?虽然一行代码简洁明了,但让 import 和 export 各司其职让事情能保持一致。
1 | // bad |
4.同一个路径只使用一次import。
为什么?相同路径有多个import会导致代码难以维护。
1 | // bad |
5.不要export可变的绑定。
为什么?避免不确定的可变量,特别是export可变的绑定。如果某些特殊情况需要使用这种场景,通常应该export常量引用。
1 | // bad |
6.模块中只有单个export,最好使用default export 。
为什么?一个文件最好只做一件事,这样更具备可读性和可维护性。
1 | // bad |
6.模块中只有单个export,最好使用default export 。
为什么?一个文件最好只做一件事,这样更具备可读性和可维护性。
1 | // bad |
7.将所有的import语句放在文件的顶部。
为什么?由于imports会被提升,最好保持它们在顶部以防出现不可预期的行为。
1 | // bad |
8.多行import应该和多行数组和对象一样有缩进。
为什么?花括号需要遵循与指南中的每个其他花括号相同的缩进规则,末尾的逗号也一样。
1 | // bad |
9.禁止在模块导入语句中使用Webpack加载器语法。
为什么?在import中使用webpack 语法会将代码耦合进bundler中。推荐在webpack.config.js中配置loader 规则。
1 | // bad |