JavaScript Style Guide

项目原地址

References

2.1 所有的赋值都用 const,避免使用 var
2.2 如果变量一定需要重新赋值,使用 let 而非 var
2.3 注意 letconst 都是块级作用域,而 var 是函数级作用域。

Object

3.1 使用字面量语法创建对象。
3.2 Use computed property names when creating objects with dynamic property names.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getKey(k) {
return `a key named ${k}`;
}

// bad
const obj = {
id: 5,
name: 'San Francisco',
};
obj[getKey('enabled')] = true;

// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
};

3.3 对象方法使用简写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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 对象属性使用简写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// const lukeSkywalker = 'Luke Skywalker';
let lukeSkywalker = 'Luke Skywalker';

// bad
const obj = {
lukeSkywalker: lukeSkywalker,
};

// good
const obj2 = {
// 寻找属性同名变量/常量,并赋值
lukeSkywalker,
};

console.log(obj2["lukeSkywalker"]); // Luke Skywalker

lukeSkywalker = "abc";

console.log(obj2["lukeSkywalker"]); // Luke Skywalker

obj2["lukeSkywalker"] = "def";

console.log(obj2["lukeSkywalker"]); // def

3.5 将简写的对象属性放在对象声明的前面。

1
2
3
4
5
6
7
8
9
10
11
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';

const obj = {
lukeSkywalker,
anakinSkywalker,
episodeOne: 1,
twoJediWalkIntoACantina: 2,
episodeThree: 3,
mayTheFourth: 4,
};

3.6 只有当属性名作为标识符非法时,才使用引号包裹。

1
2
3
4
5
6
7
8
9
10
11
12
13
// bad
const bad = {
'foo': 3,
'bar': 4,
'data-blah': 5,
};

// good
const good = {
foo: 3,
bar: 4,
'data-blah': 5,
};

3.7 不要直接调用 Object.prototype 上的方法,如 hasOwnPropertypropertyIsEnumerableisPrototypeOf

Why? These methods may be shadowed by properties on the object in question - consider { hasOwnProperty: false } - or, the object may be a null object (Object.create(null)).

1
2
3
4
5
6
7
8
9
10
11
12
// bad
console.log(object.hasOwnProperty(key));

// good
console.log(Object.prototype.hasOwnProperty.call(object, key));

// best
const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
console.log(has.call(object, key));
/* or */
import has from 'has'; // https://www.npmjs.com/package/has
console.log(has(object, key));

3.8 对象浅拷贝时,更推荐使用对象扩展语法而不是 Object.assign 。使用对象的 rest 解构运算符获取一个省略了某些属性的新对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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
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 }

Arrays

4.1 用字面量语法创建数组
4.2 用 Array#push 代替直接向数组中添加一个值。
4.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.4 使用 ... 运算符而不是 Array.from 来将一个可迭代的对象转换为数组。

1
2
3
4
5
6
7
const foo = document.querySelectorAll('.foo');

// good
const nodes = Array.from(foo);

// best
const nodes = [...foo];

4.5 使用 Array.from 将一个类数组对象转换为一个数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };

// bad
const arr = Array.prototype.slice.call(arrLike);

// good
const arr = Array.from(arrLike);

arr.forEach(console.log);
// result
// foo 0 (3) ['foo', 'bar', 'baz']
// bar 1 (3) ['foo', 'bar', 'baz']
// baz 2 (3) ['foo', 'bar', 'baz']

4.6 使用 Array.from 而不是 ... 运算符去做 map 迭代。因为这样可以避免产生中间数组。

1
2
3
4
5
// bad
const baz = [...foo].map(bar);

// good
const baz = Array.from(foo, bar);

4.7 在数组方法的回调函数中使用 return 语句。如果函数体由一条返回一个表达式的语句组成,并且这个表达式没有副作用,这个时候可以忽略 return ,详见 8.2

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
// 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 `acc` becomes undefined after the first iteration
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
const flatten = acc.concat(item);
});

// good
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
const flatten = acc.concat(item);
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.8 如果一个数组有多行,在数组的 [ 后和 ] 前断行。

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
// bad
const arr = [
[0, 1], [2, 3], [4, 5],
];

const objectInArray = [{
id: 1,
}, {
id: 2,
}];

const numberInArray = [
1, 2,
];

// good
const arr = [[0, 1], [2, 3], [4, 5]];

const objectInArray = [
{
id: 1,
},
{
id: 2,
},
];

const numberInArray = [
1,
2,
];

Destructuring

5.1 使用对象解构来访问和使用一个对象的多个属性。

Why? Destructuring saves you from creating temporary references for those properties, and from repetitive access of the object. Repeating object access creates more repetitive code, requires more reading, and creates more opportunities for mistakes. 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;

return `${firstName} ${lastName}`;
}

// good
function getFullName(user) {
const { firstName, lastName } = user;
return `${firstName} ${lastName}`;
}

// best
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}

5.2 使用数组解构

1
2
3
4
5
6
7
8
const arr = [1, 2, 3, 4];

// bad
const first = arr[0];
const second = arr[1];

// good
const [first, second] = arr;

5.3 需要多个返回值时使用对象解构而不是数组解构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// bad
function processInput(input) {
// then a miracle occurs
return [left, right, top, bottom];
}

// the caller needs to think about the order of return data
const [left, __, top] = processInput(input);

// good
function processInput(input) {
// then a miracle occurs
return { left, right, top, bottom };
}

// the caller selects only the data they need
const { left, top } = processInput(input);

Strings

6.1 字符串应使用单引号 ''
6.2 超过 100 个字符的字符串不应该连接成多行。

Why? Broken strings are painful to work with and make code less searchable.

1
2
3
4
5
6
7
8
9
10
11
12
13
// bad
const 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.';

// bad
const 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.';

// good
const 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 当需要动态生成字符串时,使用模板字符串而不是字符串拼接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// bad
function sayHi(name) {
return 'How are you, ' + name + '?';
}

// bad
function sayHi(name) {
return ['How are you, ', name, '?'].join();
}

// good
function sayHi(name) {
return `How are you, ${name}?`;
}

6.4 永远不要在字符串上使用 eval() ,该方法有太多漏洞。
6.5 不要无故的在字符串中转义字符。

Functions

7.1 使用命名函数表达式而不是函数声明

使用函数声明会发生声明提前;使用匿名函数会导致错误难以追踪。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// bad
function foo() {
// ...
}

// bad
const foo = function () {
// ...
};

// good
// lexical name distinguished from the variable-referenced invocation(s)
const short = function longUniqueMoreDescriptiveLexicalFoo() {
// ...
};

7.2 把立即执行函数表达式(IIFE)包裹在圆括号中。

1
2
3
4
// immediately-invoked function expression (IIFE)
(function () {
console.log('Welcome to the Internet. Please follow me.');
}());

7.3 不要在非函数块(if, while, etc)内声明函数,而应该将函数分配给一个变量。

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.');
};
}

7.4 函数声明不是一条语句。
7.5 参数名不要使用 arguments 。这将会使得它的优先级高于每个函数作用域自带的 arguments 对象。
7.6 不要使用 arguments,用收集参数语法 ... 代替。

Why? … is explicit about which arguments you want pulled. Plus, rest arguments are a real Array, and not merely Array-like like arguments.

1
2
3
4
5
6
7
8
9
10
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}

// good
function concatenateAll(...args) {
return args.join('');
}

7.7 用默认参数语法而不是在函数里对参数重新赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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 避免默认参数的副作用

看得出写这代码的人一心想的都是“哎,我就是玩!”

1
2
3
4
5
6
7
8
9
var b = 1;
// bad
function count(a = b++) {
console.log(a);
}
count(); // 1
count(); // 2
count(3); // 3
count(); // 3

7.9 总是把默认参数放在最后。
7.10 永远不要使用函数构造器创建函数
7.11 函数定义部分要有空格。

1
2
3
4
5
6
7
8
// bad
const f = function(){};
const g = function (){};
const h = function() {};

// good
const x = function () {};
const y = function a() {};

7.12 不要修改参数或对参数重新赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// bad
function f1(obj) {
obj.key = 1;
}

// good
function f2(obj) {
const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
}

// bad
function f1(a) {
a = 1;
// ...
}

// good
function f3(a) {
const b = a || 1;
// ...
}

7.13 尽量使用扩展运算符 ... 调用多参数的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
// bad
const x = [1, 2, 3, 4, 5];
console.log.apply(console, x);

// good
const x = [1, 2, 3, 4, 5];
console.log(...x);

// bad
new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));

// good
new Date(...[2016, 8, 5]);

7.14 调用或者声明一个包含多个参数的函数,应该像这个指南里的代码一样缩进——即每行只包含一个参数,以逗号结尾。

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
// bad
function foo(bar,
baz,
quux) {
// ...
}

// good
function foo(
bar,
baz,
quux,
) {
// ...
}

// bad
console.log(foo,
bar,
baz);

// good
console.log(
foo,
bar,
baz,
);

Arrow Functions

8.1 当你必须使用一个匿名函数时,使用箭头函数。

Why? It creates a version of the function that executes in the context of this, which is usually what you want, and is a more concise syntax.

Why not? If you have a fairly complicated function, you might move that logic out into its own named function expression.

1
2
3
4
5
6
7
8
9
10
11
// 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 语句。

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
// 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,
}));

// No implicit return with side effects
function foo(callback) {
const val = callback();
if (val === true) {
// Do something if callback returns true
}
}

let bool = false;

// bad
foo(() => bool = true);

// good
foo(() => {
bool = true;
});

8.3 如果表达式跨越多行,为了更好的可读性可以将它包裹在圆括号中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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 为了清晰和一致性,总是将参数包裹在圆括号中。
8.5 Enforce the location of arrow function bodies with implicit returns.

1
2
3
4
5
6
7
8
9
10
11
12
13
// bad
(foo) =>
bar;

(foo) =>
(bar);

// good
(foo) => bar;
(foo) => (bar);
(foo) => (
bar
)

Classes & Constructors

9.1 总是使用 class 。避免直接操纵 prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// bad
function Queue(contents = []) {
this.queue = [...contents];
}
Queue.prototype.pop = function () {
const value = this.queue[0];
this.queue.splice(0, 1);
return value;
};

// good
class Queue {
constructor(contents = []) {
this.queue = [...contents];
}
pop() {
const value = this.queue[0];
this.queue.splice(0, 1);
return value;
}
}

9.2 使用 extends 实现继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// bad
const inherits = require('inherits');
function PeekableQueue(contents) {
Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function () {
return this.queue[0];
};

// good
class PeekableQueue extends Queue {
peek() {
return this.queue[0];
}
}

9.3 方法可以返回 this 来帮助实现链式调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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 自己实现 toString() 方法是可以的,但需要保证它可以正常工作并且没有副作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Jedi {
constructor(options = {}) {
this.name = options.name || 'no name';
}

getName() {
return this.name;
}

toString() {
return `Jedi - ${this.getName()}`;
}
}

9.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';
}
}

9.6 避免重复定义类成员

Why? Duplicate class member declarations will silently prefer the last one - having duplicates is almost certainly a bug.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// bad
class Foo {
bar() { return 1; }
bar() { return 2; }
}

// good
class Foo {
bar() { return 1; }
}

// good
class Foo {
bar() { return 2; }
}

9.7 除非外部库或框架需要使用特定的非静态方法,否则类方法应该使用 this 或被定义为静态方法。实例方法表明了它应该根据实例的属性有不同的行为。

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
// bad
class Foo {
bar() {
console.log('bar');
}
}

// good - this is used
class Foo {
bar() {
console.log(this.bar);
}
}

// good - constructor is exempt
class Foo {
constructor() {
// ...
}
}

// good - static methods aren't expected to use this
class Foo {
static bar() {
console.log('bar');
}
}

Modules

10.1 总是使用 (import / export) 模块而不是非标准的模块系统。你可以随时转到你更喜欢的模块系统。

1
2
3
4
5
6
7
8
9
10
11
// bad
const AirbnbStyleGuide = require('./AirbnbStyleGuide');
module.exports = AirbnbStyleGuide.es6;

// ok
import AirbnbStyleGuide from './AirbnbStyleGuide';
export default AirbnbStyleGuide.es6;

// best
import { es6 } from './AirbnbStyleGuide';
export default es6;

10.2 不用使用通配符导入。

1
2
3
4
5
// bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';

// good
import AirbnbStyleGuide from './AirbnbStyleGuide';

10.3 不要从 import 中直接 export
10.4 一个路径只导入一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
// bad
import foo from 'foo';
// … some other imports … //
import { named1, named2 } from 'foo';

// good
import foo, { named1, named2 } from 'foo';

// good
import foo, {
named1,
named2,
} from 'foo';

10.5 不要导出可变的绑定。

1
2
3
4
5
6
7
// bad
let foo = 3;
export { foo };

// good
const foo = 3;
export { foo };

10.6 在一个单一导出模块中,用 export default 更好。

1
2
3
4
5
// bad
export function foo() {}

// good
export default function foo() {}

10.7 把 import 语句放在所有其他语句之前。
10.8 多行 import 应该缩进。

1
2
3
4
5
6
7
8
// good
import {
longNameA,
longNameB,
longNameC,
longNameD,
longNameE,
} from 'path';

10.9 在 import 语句中不允许 Webpack loader 语法。

1
2
3
4
5
6
7
// bad
import fooSass from 'css!sass!foo.scss';
import barCss from 'style!css!bar.css';

// good
import fooSass from 'foo.scss';
import barCss from 'bar.css';

10.10 导入 JavaScript 文件时不要包含扩展名。

Iterators and Generators

11.1 不要使用迭代器。使用 JavaScript 高阶函数代替 for-infor-of 循环。

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
const numbers = [1, 2, 3, 4, 5];

// bad
let sum = 0;
for (let num of numbers) {
sum += num;
}
sum === 15;

// good
let 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;

// bad
const increasedByOne = [];
for (let i = 0; i < numbers.length; i++) {
increasedByOne.push(numbers[i] + 1);
}

// good
const increasedByOne = [];
numbers.forEach((num) => {
increasedByOne.push(num + 1);
});

// best (keeping it functional)
const increasedByOne = numbers.map((num) => num + 1);

11.2 现在暂时不要使用生成器。
11.3 如果你一定要使用生成器,请确保它们的函数签名空格得当。

Why? function and * are part of the same conceptual keyword - * is not a modifier for function, function* is a unique construct, different from function.

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
42
43
44
45
46
47
48
49
50
51
52
53
// bad
function * foo() {
// ...
}

// bad
const bar = function * () {
// ...
};

// bad
const baz = function *() {
// ...
};

// bad
const quux = function*() {
// ...
};

// bad
function*foo() {
// ...
}

// bad
function *foo() {
// ...
}

// very bad
function
*
foo() {
// ...
}

// very bad
const wat = function
*
() {
// ...
};

// good
function* foo() {
// ...
}

// good
const foo = function* () {
// ...
};

Properties

12.1 访问属性时使用点 . 语法。

1
2
3
4
5
6
7
8
9
10
const luke = {
jedi: true,
age: 28,
};

// bad
const isJedi = luke['jedi'];

// good
const isJedi = luke.jedi;

12.2 当使用变量获取属性时用 []
12.3 做幂运算时使用幂运算符 **

1
2
3
4
5
// bad
const binary = Math.pow(2, 10);

// good
const binary = 2 ** 10;

Variables

13.1 使用 constlet 每次仅声明一个变量/常量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// bad
const items = getItems(),
goSportsTeam = true,
dragonball = 'z';

// bad
// (compare to above, and try to spot the mistake)
const items = getItems(),
goSportsTeam = true;
dragonball = 'z';

// good
const items = getItems();
const goSportsTeam = true;
const dragonball = 'z';

13.2 把 const 声明的常量 和 let 声明的变量分别放在一起。

1
2
3
4
5
6
// good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;

13.3 不要链式声明变量

Why? Chaining variable assignments creates implicit global variables.

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
// bad
(function example() {
// JavaScript interprets this as
// let a = ( b = ( c = 1 ) );
// The let keyword only applies to variable a; variables b and c become
// global variables.
let a = b = c = 1;
}());

console.log(a); // throws ReferenceError
console.log(b); // 1
console.log(c); // 1

// good
(function example() {
let a = 1;
let b = a;
let c = a;
}());

console.log(a); // throws ReferenceError
console.log(b); // throws ReferenceError
console.log(c); // throws ReferenceError

// the same applies for `const`

13.4 避免使用一元自增自减运算符(++, --)。
13.5 赋值时避免在 = 前/后换行。如果你的赋值语句超过 max-len ,那就用圆括号把这个值包起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// bad
const foo =
superLongLongLongLongLongLongLongLongFunctionName();

// bad
const foo
= 'superLongLongLongLongLongLongLongLongString';

// good
const foo = (
superLongLongLongLongLongLongLongLongFunctionName()
);

// good
const foo = 'superLongLongLongLongLongLongLongLongString';

Hoisting

14.1 var declarations get hoisted to the top of their closest enclosing function scope, their assignment does not. const and let declarations are blessed with a new concept called Temporal Dead Zones (TDZ). It’s important to know why typeof is no longer safe.

Comparison Operators & Equality

15.1 使用 ===!== 而不是 ==!=
15.2 布尔值要用缩写,但字符串和数字要明确使用比较操作符。

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
// bad
if (isValid === true) {
// ...
}

// good
if (isValid) {
// ...
}

// bad
if (name) {
// ...
}

// good
if (name !== '') {
// ...
}

// bad
if (collection.length) {
// ...
}

// good
if (collection.length > 0) {
// ...
}

15.3 在 casedefault 子句里用大括号创建一块包含词法声明的区域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// good
switch (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.4 三元表达式不应该嵌套,而且一般是单行表达式。
15.5 避免不必要的三元表达式

1
2
3
4
5
6
7
8
9
// 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;

Blocks

16.1 对所有多行代码块使用大括号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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;
}

Control Statements

17.1 Don’t use selection operators in place of control statements.

1
2
3
4
5
6
7
// bad
!isRunning && startRunning();

// good
if (!isRunning) {
startRunning();
}

Comments

18.1 Use /** ... */ for multiline comments.
18.2 Use // for single line comments. Place single line comments on a newline above the subject of the comment. Put an empty line before the comment unless it’s on the first line of a block.

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
42
// 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;
}

// bad
function getType() {

// 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 Start all comments with a space to make it easier to read.
18.4 Use // FIXME: to annotate problems.

1
2
3
4
5
6
7
8
class Calculator extends Abacus {
constructor() {
super();

// FIXME: shouldn’t use a global here
total = 0;
}
}

18.5 Use // TODO: to annotate solutions to problems.

1
2
3
4
5
6
7
8
class Calculator extends Abacus {
constructor() {
super();

// TODO: total should be configurable by an options param
this.total = 0;
}
}

Whitespace

19.1 Use soft tabs (space character) set to 2 spaces.
19.2 { 前放置一个空格。
19.3 Place 1 space before the opening parenthesis in control statements (if, while etc.). Place no space between the argument list and the function name in function calls and declarations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// bad
if(isJedi) {
fight ();
}

// good
if (isJedi) {
fight();
}

// bad
function fight () {
console.log ('Swooosh!');
}

// good
function fight() {
console.log('Swooosh!');
}

19.4 用空格隔开运算符。
19.5 文件结尾空一行。
19.6 Use indentation when making long method chains (more than 2 method chains). Use a leading dot, which emphasizes that the line is a method call, not a new statement.

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
// 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);
const svg = leds.enter().append('svg:svg');
svg.classed('led', true).attr('width', (radius + margin) * 2);
const g = svg.append('svg:g');
g.attr('transform', `translate(${radius + margin},${radius + margin})`).call(tron.led);

19.7 Leave a blank line after blocks and before the next statement.
19.8 Do not pad your blocks with blank lines.

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
// 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 Do not use multiple blank lines to pad your code.
19.10 圆括号里侧不要加空格。
19.11 方括号里侧不要加空格。
19.12 花括号里侧要加空格。
19.13 Avoid having lines of code that are longer than 100 characters (including whitespace). Note: per above, long strings are exempt from this rule, and should not be broken up.
19.14 Avoid spaces before commas and require a space after commas.
19.15 Enforce spacing between keys and values in object literal properties.

1
2
// good
var obj = { foo: 42 };

19.16 行末不要有空格
19.17 文件开始处不要出现空行

Commas

20.1 不要前置逗号

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
// 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 额外结尾逗号

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
42
43
44
45
46
47
48
49
50
51
52
53
54
// bad - git diff without trailing comma
const hero = {
firstName: 'Florence',
lastName: 'Nightingale',
inventorOf: ['coxcomb chart', 'modern nursing']
};

// good - git diff with trailing comma
const hero = {
firstName: 'Florence',
lastName: 'Nightingale',
inventorOf: ['coxcomb chart', 'modern nursing'],
};


// bad
const heroes = [
'Batman',
'Superman'
];

// good
const heroes = [
'Batman',
'Superman',
];

// bad
function createHero(
firstName,
lastName,
inventorOf
) {
// does nothing
}

// good
function createHero(
firstName,
lastName,
inventorOf,
) {
// does nothing
}

// good (note that a comma must not appear after a "rest" element)
function createHero(
firstName,
lastName,
inventorOf,
...heroArgs
) {
// does nothing
}

Semicolons

21.1 别忘分号

Why? When JavaScript encounters a line break without a semicolon, it uses a set of rules called Automatic Semicolon Insertion to determine whether it should regard that line break as the end of a statement, and (as the name implies) place a semicolon into your code before the line break if it thinks so. ASI contains a few eccentric behaviors, though, and your code will break if JavaScript misinterprets your line break. These rules will become more complicated as new features become a part of JavaScript. Explicitly terminating your statements and configuring your linter to catch missing semicolons will help prevent you from encountering issues.

Type Casting & Coercion

22.1 在语句开始处执行强制类型转换。
22.2 Strings

1
2
3
4
5
6
7
8
9
10
11
12
13
// => 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 Numbers: 使用 Number 做类型转换,使用 parseInt 转换字符串时应总是带上基数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const inputValue = '4';

// bad
const val = new Number(inputValue);

// bad
const val = +inputValue;

// bad
const val = inputValue >> 0;

// bad
const val = parseInt(inputValue);

// good
const val = Number(inputValue);

// good
const val = parseInt(inputValue, 10);

22.4 If for whatever reason you are doing something wild and parseInt is your bottleneck and need to use Bitshift for performance reasons, leave a comment explaining why and what you’re doing.

1
2
3
4
5
6
7
// good
/**
* parseInt was the reason my code was slow.
* Bitshifting the String to coerce it to a
* Number made it a lot faster.
*/
const val = inputValue >> 0;

22.5 Note: Be careful when using bitshift operations. Numbers are represented as 64-bit values, but bitshift operations always return a 32-bit integer (source). Bitshift can lead to unexpected behavior for integer values larger than 32 bits.

22.6 布尔值

1
2
3
4
5
6
7
8
9
10
const age = 0;

// bad
const hasAge = new Boolean(age);

// good
const hasAge = Boolean(age);

// best
const hasAge = !!age;

Name Conventions

23.1 避免用一个字母命名
23.2 Use camelCase when naming objects, functions, and instances.
23.3 Use PascalCase only when naming constructors or classes.
23.4 命名时不要用前置或后置下划线
23.5 不要保存 this 引用。使用箭头函数或函数绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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 基本文件名应能精确匹配它的默认导出的名字。

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
// file 1 contents
class CheckBox {
// ...
}
export default CheckBox;

// file 2 contents
export default function fortyTwo() { return 42; }

// file 3 contents
export default function insideDirectory() {}

// in some other file
// bad
import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename
import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export
import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export

// bad
import CheckBox from './check_box'; // PascalCase import/export, snake_case filename
import forty_two from './forty_two'; // snake_case import/filename, camelCase export
import inside_directory from './inside_directory'; // snake_case import, camelCase export
import index from './inside_directory/index'; // requiring the index file explicitly
import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly

// good
import CheckBox from './CheckBox'; // PascalCase export/import/filename
import fortyTwo from './fortyTwo'; // camelCase export/import/filename
import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index"
// ^ supports both insideDirectory.js and insideDirectory/index.js

23.7 当你 export-default 一个函数时,函数名使用 camelCase ,文件名需要和函数名一致
23.8 当你 export 一个 结构体/类/单例/函数库/对象 时用 PascalCase。

1
2
3
4
5
6
const AirbnbStyleGuide = {
es6: {
},
};

export default AirbnbStyleGuide;

23.9 简写和缩写应全部大写或小写。

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
// bad
import SmsContainer from './containers/SmsContainer';

// bad
const HttpRequests = [
// ...
];

// good
import SMSContainer from './containers/SMSContainer';

// good
const HTTPRequests = [
// ...
];

// also good
const httpRequests = [
// ...
];

// best
import TextMessageContainer from './containers/TextMessageContainer';

// best
const requests = [
// ...
];

23.10 你可以用全大写字母设置常量,它需要满足下列三个条件:

  1. 它是导出变量
  2. const 定义
  3. 这个变量是可信的,它和它的子属性都是不能被改变的
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
// 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';

// ---

// allowed but does not supply semantic value
export const apiKey = 'SOMEKEY';

// better in most cases
export const API_KEY = 'SOMEKEY';

// ---

// bad - unnecessarily uppercases key while adding no semantic value
export const MAPPING = {
KEY: 'value'
};

// good
export const MAPPING = {
key: 'value'
};

Accessors

24.1 不需要使用属性的访问器函数
24.2 不要使用 JavaScript 的 getters/setters 。相反,你可以用 getVal()setVal('hello') 去创建你自己的访问器函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// bad
class Dragon {
get age() {
// ...
}

set age(value) {
// ...
}
}

// good
class Dragon {
getAge() {
// ...
}

setAge(value) {
// ...
}
}

24.3 如果 属性/方法 是 boolean ,使用 isVal()hasVal()

1
2
3
4
5
6
7
8
9
// bad
if (!dragon.age()) {
return false;
}

// good
if (!dragon.hasAge()) {
return false;
}

24.4 创建 get()set() 函数是可以的,但是要一起用。

Events

25.1 When attaching data payloads to events (whether DOM events or something more proprietary like Backbone events), pass an object literal (also known as a “hash”) instead of a raw value. This allows a subsequent contributor to add more data to the event payload without finding and updating every handler for the event.

jQuery

26.1 为 jQuery 对象变量附上前缀 $

1
2
3
4
5
6
7
8
// bad
const sidebar = $('.sidebar');

// good
const $sidebar = $('.sidebar');

// good
const $sidebarBtn = $('.sidebar-btn');

26.2 缓存 jQuery 查询。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// bad
function setSidebar() {
$('.sidebar').hide();

// ...

$('.sidebar').css({
'background-color': 'pink',
});
}

// good
function setSidebar() {
const $sidebar = $('.sidebar');
$sidebar.hide();

// ...

$sidebar.css({
'background-color': 'pink',
});
}

26.3 DOM 查询使用 $('.sidebar ul')$('.sidebar > ul')
26.4 Use find with scoped jQuery object queries.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// bad
$('ul', '.sidebar').hide();

// bad
$('.sidebar').find('ul').hide();

// good
$('.sidebar ul').hide();

// good
$('.sidebar > ul').hide();

// good
$sidebar.find('ul').hide();

ECMAScript 5 Compatibility

27.1 参考 Kangax 的 ES5 兼容性表格

ECMAScript 6+ (ES 2015+) Styles

28.1 下面是收集到的各种 ES6+ 特性的链接。

  1. Arrow Functions
  2. Classes
  3. Object Shorthand
  4. Object Concise
  5. Object Computed Properties
  6. Template Strings
  7. Destructuring
  8. Default Parameters
  9. Rest
  10. Array Spreads
  11. Let and Const
  12. Exponentiation Operator
  13. Iterators and Generators
  14. Modules

28.2 不要使用 TC39 proposals

Standard Library

29.1 使用 Number.isNaN 代替全局的 isNaN
29.2 使用 Number.isFinite 代替全局的 isFinite

Testing

30.1 No, but seriously:

  • Whichever testing framework you use, you should be writing tests!
  • 尽量去写很多小而美的纯函数(pure functions),从而减少突变的发生。
  • 小心 stub 和 mock —— 它们会让你的测试变得脆弱。
  • 100% test coverage is a good goal to strive for, even if it’s not always practical to reach it.
  • 每当你修复一个 bug ,都要写一个回归测试。在没有回归测试的情况下修复的 bug 几乎肯定会在将来再次出现。

Performance

Resources

Learning ES6+

Read This

Tools

Other Style Guides

Other Styles

Further Reading

Books

Blogs

Podcasts(播客)

In the Wild

This is a list of organizations that are using this style guide.