ECMAScript6
ECMAScript6
最近刚开始做一个 React项目,从理论到真正的实践, 因为自己太笨了导致这个熟悉的过程是痛苦而艰辛的,好在目前为止对React的使用越来越熟悉。
再说说 ES6, 在这个项目中用到了ES6中的少部分内容, 参考中文文档做一个再次梳理, 不是总结,而是复习,加深印象。
类型(Types)
1.1 基本类型(Primitives): 直接存取基本类型
- 字符串(string)
- 数值(number)
- 布尔类型(boolean)
- null
- undefined
const foo = 1;
let bar = foo;
bar = 9;
console.log(foo, bar); // => 1, 9
1.2 复杂类型(Complex): 通过引用的方式存取复杂类型
- 对象
- 数组
- 函数
const foo = [1, 2];
const bar = foo;
bar[0] = 9;
console.log(foo[0], bar[0]); // => 9, 9
引用(References)
2.1 对所有的引用使用 const;避免使用 var。
為什麼?因為這能確保你無法對參考重新賦值,也不會讓你的程式碼有錯誤或難以理解。
// bad
var a = 1;
var b = 2;
// good
const a = 1;
const b = 2;
2.2 如果你需要可变动的引用,使用 let 代替 var
为什么?因为 let 是块级作用域,而 var 是函数作用域。
// bad
var 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); // ReferenceError
console.log(b); // ReferenceError
对象(Objects)
3.1 使用字面值创建对象。
// bad
const item = new Object();
// good
const item = {};
3.2 创建有动态属性名的对象时,使用实时计算属性名称。
为什么?因为这样可以让你在一个地方定义所有的对象属性。
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 使用对象方法的简写。
// 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 使用对象属性值的简写。
为什么?因为这样更短更有描述性。
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
lukeSkywalker: lukeSkywalker,
};
// good
const obj = {
lukeSkywalker,
};
3.5 在对象声明的一开始,把简写的属性分组列出。
为什么?因为这样能清楚地看出哪些属性使用了简写。
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
episodeOne: 1,
twoJediWalkIntoACantina: 2,
lukeSkywalker,
episodeThree: 3,
mayTheFourth: 4,
anakinSkywalker,
};
// good
const obj = {
lukeSkywalker,
anakinSkywalker,
episodeOne: 1,
twoJediWalkIntoACantina: 2,
episodeThree: 3,
mayTheFourth: 4,
};
3.6 只对包含非法标识符的对象属性使用引号。
为什么呢?一般来说这样做使代码可读性更强。辅助提高代码高亮,并且便于许多JS引擎进行优化。
// bad
const bad = {
'foo': 3,
'bar': 4,
'data-blah': 5,
};
// good
const good = {
foo: 3,
bar: 4,
'data-blah': 5,
};
3.7 对于某个对象obj,不要直接访问obj.prototype上的方法,如 hasOwnProperty, propertyIsEnumerable, 和 isPrototypeOf.
为什么呢?这些方法可能被重新定义,像这种情况 { hasOwnProperty: false } – 或者可能是个空对象 (Object.create(null)).
// 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.
/* or */
import has from 'has';
// ...
console.log(has.call(object, key));
3.8 进行浅拷贝时,使用扩展运算符替代Object.assign 另外,使用…进行结构赋值,可以创建出含有不确定属性的新对象。
// 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 }
数组(Arrays)
4.1 使用字面值创建数组。
const items = new Array();
// good
const items = [];
4.2 向数组添加元素时使用 push 替代直接赋值
const someStack = [];
// bad
someStack[someStack.length] = 'abracadabra';
// good
someStack.push('abracadabra');
4.3 使用扩展运算符 … 复制数组。
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
4.4 使用 Array.from 把一个类数组对象转换成数组。
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
4.5 在数组的回调中,如果一个函数适合用一行写出并且只有一个参数,那就把花括号、圆括号和 return 都省略掉。如果不是,那就不要省略。
// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map(x => x + 1);
// bad
const flat = {};
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
const flatten = memo.concat(item);
flat[index] = memo.concat(item);
});
// good
const flat = {};
[[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
const flatten = memo.concat(item);
flat[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.6 对于一个多行数组,可以在数组的 [ 和 ] 之间罗列其每一项。
// 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 使用解构存取和使用多属性对象。
为什么?因为解构能减少临时引用属性。
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
// good
function getFullName(obj) {
const { firstName, lastName } = obj;
return `${firstName} ${lastName}`;
}
// best
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
5.2 对数组使用解构赋值。
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
5.3 需要回传多个值时,使用对象解构,而不是数组解构。
为什么?增加属性或者改变排序不会改变调用时的位置。
// bad
function processInput(input) {
// then a miracle occurs
return [left, right, top, bottom];
}
// 调用时需要考虑回调数据的顺序。
const [left, __, top] = processInput(input);
// good
function processInput(input) {
// then a miracle occurs
return { left, right, top, bottom };
}
// 调用时只选择需要的数据
const { left, right } = processInput(input);
字符串
6.1 字符串使用单引号 ‘ ’ 。
// bad
const name = "Capt. Janeway";
// good
const name = 'Capt. Janeway';
6.2 字符串超过 100 个字符应该使用字符串连接号换行。
// 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 程序化生成字符串时,使用模板字符串代替字符串连接。
为什么?模板字符串更为简洁,更具可读性。
// 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 不使用不必要的转义字符。
为什么?反斜杠降低代码的可读性,所以尽可能在必要时再采用反斜杠。
// bad
const foo = '\'this\' \i\s \"quoted\"';
// good
const foo = '\'this\' is "quoted"';
const foo = `my name is '${name}'`;
函数
7.1 使用函数声明代替函数表达式。
为什么?因为函数声明是可命名的,所以他们在调用栈中更容易被识别。此外,函数声明会把整个函数提升(hoisted),而函数表达式只会把函数的引用变量名提升。这条规则使得箭头函数可以取代函数表达式。eslint: func-style jscs: disallowFunctionDeclarations 另外,如果需要定义的函数非常复杂或者函数体过大,以至于影响到文件其他部分的阅读,最好将这部分代码变成独立的模块,并使用命名的函数表达式,否则当错误出现时,匿名函数很难定位错误堆栈中的位置。(针对该话题的讨论)
// bad
const foo = function () {
//...
};
// good
function foo() {
//...
}
// best
const foo = function bar() {
// ...
};
7.2 用圆括号包装立即执行函数表达式。
为什么呢?立即执行函数表达式被视为一个独立的单元,这是最清晰表达的方式。不过你得知道,现在模块化开发已通行全球,几乎用不上IIFE了。
// 立即函式(IIFE)
(function () {
console.log('Welcome to the Internet. Please follow me.');
}());
7.3 永远不要在一个非函数代码块(if、while 等)中声明一个函数,把那个函数赋给一个变量。浏览器允许你这么做,但它们的解析表现不一致。
7.4 注意: ECMA-262 把 block 定义为一组语句。
// bad
if (currentUser) {
function test() {
console.log('Nope.');
}
}
// good
let test;
if (currentUser) {
test = () => {
console.log('Yup.');
};
}
7.5 永远不要把参数命名为 arguments。
// bad
function nope(name, options, arguments) {
// ...stuff...
}
// good
function yup(name, options, args) {
// ...stuff...
}
7.6 不要使用 arguments。可以选择 rest 语法 … 替代。
为什么?使用 … 能明确你要传入的参数。另外 rest 参数是一个真正的数组,而 arguments 是一个类数组。
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
7.7 直接给函数的参数指定默认值,不要使用一个变化的函数参数。
// really bad
function handleThings(opts) {
// 别!我们不应该改变函数参数。
// 更加糟糕: 如果参数 opts 是 false 的话,它就会被设定为一个对象。
// 但这样的写法会造成一些 Bugs。
//(译注:例如当 opts 被赋值为空字符串,opts 仍然会被下一行代码设定为一个空对象。)
opts = opts || {};
// ...
}
// still bad
function handleThings(opts) {
if (opts === void 0) {
opts = {};
}
// ...
}
// good
function handleThings(opts = {}) {
// ...
}
7.8 给函数增加参数默认值时需要避免副作用。
为什么?因为这样的写法让人感到很困惑。
var b = 1;
// bad
function count(a = b++) {
console.log(a);
}
count(); // 1
count(); // 2
count(3); // 3
count(); // 3
7.9 把默认参数放在最后。
// bad
function handleThings(opts = {}, name) {
// ...
}
// good
function handleThings(name, opts = {}) {
// ...
}
7.10 不要用函数构造器创建新函数。
为什么呢?这种创建方式处理字符串时与eval()类似,容易产生隐患。
// bad
var add = new Function('a', 'b', 'return a + b');
// still bad
var subtract = Function('a', 'b', 'return a - b');
7.11 用空格分离函数体和函数名
// bad
const f = function(){};
const g = function (){};
const h = function() {};
// good
const x = function () {};
const y = function a() {};
7.12 避免改变传入的参数
为什么呢?传入参数为对象时,容易因为改变参数本身而产生副作用。
// bad
function f1(obj) {
obj.key = 1;
}
// good
function f2(obj) {
const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
}
7.13 不要对参数重新赋值。
Why? Reassigning parameters can lead to unexpected behavior, especially when accessing the arguments object. It can also cause optimization issues, especially in V8.
// bad
function f1(a) {
a = 1;
// ...
}
function f2(a) {
if (!a) { a = 1; }
// ...
}
// good
function f3(a) {
const b = a || 1;
// ...
}
function f4(a = 1) {
// ...
}
7.14 使用展开运算符应对可变参数
主要为了简洁,不需要调用apply或者new。
// 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.15 函数多个参数时的写法.
// 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 当你必须使用函数表达式(或传递一个匿名函数)时,使用箭头函数符号。
为什么?因为箭头函数创造了新的一个 this 执行环境(译注:参考 Arrow functions – JavaScript | MDN 和 ES6 arrow functions, syntax and lexical scoping),通常情况下都能满足你的需求,而且这样的写法更为简洁。
为什么不?如果你有一个相当复杂的函数,你或许可以把逻辑部分转移到一个函数声明上。
// 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 都省略掉。如果不是,那就不要省略。
为什么?语法糖。在链式调用中可读性很高。
为什么不?当你打算回传一个对象的时候。
// 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,
}));
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 如果箭头函数只有一个参数并且不包含{}时,可以对传入参数省略圆括号,除此外需要明确地将参数包裹起来。
减少视觉混乱.
// 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 避免在箭头函数里使用比较运算符(<=, >=),以免产生混淆。
// 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;
};
构造器(Constructors)
9.1 总是使用 class。避免直接操作 prototype 。
为什么? 因为 class 语法更为简洁更易读。
// 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 继承。
为什么?因为 extends 是一个内建的原型继承方法并且不会破坏 instanceof。
// 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 来帮助链式调用。
// 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 可以写一个自定义的 toString() 方法,但要确保它能正常运行并且不会引起副作用。
class Jedi {
constructor(options = {}) {
this.name = options.name || 'no name';
}
getName() {
return this.name;
}
toString() {
return `Jedi - ${this.getName()}`;
}
}
9.5 一般地,类都有一个默认构造器。空构造器或者只继承父类型的构造器是不必要的。
// 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 避免重复声明类成员。
重复声明的类成员会以最后一个为准。重复定义可以被视为一个BUG。
// bad
class Foo {
bar() { return 1; }
bar() { return 2; }
}
// good
class Foo {
bar() { return 1; }
}
// good
class Foo {
bar() { return 2; }
}
模块
10.1 总是使用模组 (import/export) 而不是其他非标准模块系统。你可以编译为你喜欢的模块系统。
为什么?模块就是未来,让我们开始迈向未来吧。
// 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 不要使用通配符 import。
为什么?这样能确保你只有一个默认 export。
// bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';
// good
import AirbnbStyleGuide from './AirbnbStyleGuide';
10.3 不要从 import 中直接 export。
为什么?虽然一行代码简洁明了,但让 import 和 export 各司其职让事情能保持一致。
// bad
// filename es6.js
export { es6 as default } from './airbnbStyleGuide';
// good
// filename es6.js
import { es6 } from './AirbnbStyleGuide';
export default es6;
10.4 从同一路径下导入不同模块,不要分开写。
为了代码便于维护。
// 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 不要输出可变参数。
// bad
let foo = 3;
export { foo };
// good
const foo = 3;
export { foo }
10.6 当模块只有一个export时,使用export default。
// bad
export function foo() {}
// good
export default function foo() {}
10.7 把所有import放在其他语句之前。
将import提升到最顶部防止意外发生。
// bad
import foo from 'foo';
foo.init();
import bar from 'bar';
// good
import foo from 'foo';
import bar from 'bar';
foo.init();
10.8 导入多项时需要缩进,类似多行数组或者对象字面值。
大括号的使用在本规范中多次被提及,要保持一致性。
// bad
import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';
// good
import {
longNameA,
longNameB,
longNameC,
longNameD,
longNameE,
} from 'path';
10.9 导入模块时,不要使用Webpack loader语法。
使用webpack语法会将模块内容与代码联系起来(应该降低耦合性),推荐在 webpack.config.js中使用loader语法。
// 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';
迭代器和生成器(Iterators and Generators)
11.1 不要使用迭代器。使用高阶函数例如 map() 和 reduce() 替代 for-in或者for-of。
为什么?这加强了我们不变的规则。处理纯函数的回调值更易读,这比它带来的副作用更重要。建议使用map() / every() / filter() / find() / findIndex() / reduce() / some() / 处理数组迭代,Object.keys() / Object.values() / Object.entries() 处理对象迭代。
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 现在还不要使用 generators。
为什么?因为它们现在还没法很好地编译到 ES5。
11.3 如果必须使用生成器,那么忽略11.2的建议,并遵循以下函数标识符的空格使用方式。
为什么呢?因为 function 和 * 在概念是是联系在一起的,而同时 * 不是 function 的修饰, function* 是一种特殊的构造函数, 要与 function 作区分。
// 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 使用 . 来访问对象的属性。
const luke = {
jedi: true,
age: 28,
};
// bad
const isJedi = luke['jedi'];
// good
const isJedi = luke.jedi;
12.2 当通过变量访问属性时使用中括号 []。
const luke = {
jedi: true,
age: 28,
};
function getProp(prop) {
return luke[prop];
}
const isJedi = getProp('jedi');
变量(Variables)
13.1 一直使用 const 来声明变量,如果不这样做就会产生全局变量。我们需要避免全局命名空间的污染。
// bad
superPower = new SuperPower();
// good
const superPower = new SuperPower();
13.2 使用 const 声明每一个变量。
为什么?增加新变量将变的更加容易,而且你永远不用再担心调换错 ; 跟 ,。
// bad
const items = getItems(),
goSportsTeam = true,
dragonball = 'z';
// bad
// (与上面代码作对比,发现其中的错误)
const items = getItems(),
goSportsTeam = true;
dragonball = 'z';
// good
const items = getItems();
const goSportsTeam = true;
const dragonball = 'z';
13.3 将所有的 const 和 let 分组
为什么?当你需要把已赋值变量赋值给未赋值变量时非常有用。
// bad
let i, len, dragonball,
items = getItems(),
goSportsTeam = true;
// bad
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;
// good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;
13.4 在你需要的地方给变量赋值,但请把它们放在一个合理的位置。
为什么?let 和 const 是块级作用域而不是函数作用域。
// bad - unnecessary function call
function checkName(hasName) {
const name = getName();
if (hasName === 'test') {
return false;
}
if (name === 'test') {
this.setName('');
return false;
}
return name;
}
// good
function checkName(hasName) {
if (hasName === 'test') {
return false;
}
const name = getName();
if (name === 'test') {
this.setName('');
return false;
}
return name;
}
13.5 不要链式赋值
因为链式赋值会产生隐含的全局变量
// 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); // undefined
console.log(b); // 1
console.log(c); // 1
// good
(function example() {
let a = 1;
let b = a;
let c = a;
}());
console.log(a); // undefined
console.log(b); // undefined
console.log(c); // undefined
// 对于`const`同理
13.6 避免使用一元运算符,如++, — 。
原因:根据eslint文档描述的,一元运算符容易被自动嵌入的分号影响,引起潜在错误。使用 num += 1 替代 num++ or num ++使表意更清楚。
// bad
const 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++;
}
}
// good
const 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;
提升(Hoisting)
14.1 var 声明会被提升至该作用域的顶部,但它们赋值不会提升。let 和 const 被赋予了一种称为「暂时性死区(Temporal Dead Zones, TDZ)」的概念。这对于了解为什么 type of 不再安全相当重要。
// 以下是运行不了的 (假定没有notDefined这个全局变量)
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 匿名函数表达式的变量名会被提升,但函数内容并不会。
function example() {
console.log(anonymous); // => undefined
anonymous(); // => TypeError anonymous is not a function
var anonymous = function() {
console.log('anonymous function expression');
};
}
14.3 命名的函数表达式的变量名会被提升,但函数名和函数内容并不会。
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 函数声明的名称和函数体都会被提升。
function example() {
superPower(); // => Flying
function superPower() {
console.log('Flying');
}
}
比较运算符、等号
15.1 优先使用 === 和 !== 而不是 == 和 !=。
15.2 条件表达式例如 if 语句通过抽象方法 ToBoolean 强制计算它们的表达式并且总是遵守下面的规则:
- 对象 被计算为 true
- Undefined 被计算为 false
- Null 被计算为 false
- 布尔值 被计算为 布尔的值
- 数字 如果是 +0、-0、或 NaN 被计算为 false, 否则为 true
- 字符串如果是空字符串 ‘’ 被计算为 false,否则为 true
if ([0] && []) {
// true
// an array (even an empty one) is an object, objects will evaluate to true
}
15.3 boolean数据使用简写,string和number型例外。
// bad
if (isValid === true) {
// ...
}
// good
if (isValid) {
// ...
}
// bad
if (name) {
// ...
}
// good
if (name !== '') {
// ...
}
// bad
if (collection.length) {
// ...
}
// good
if (collection.length > 0) {
// ...
}
15.4 想了解更多信息,参考 Angus Croll 的Truth Equality and JavaScript
15.5 在switch中对case、default使用“块”来包裹含有以下词汇声明。
原因:词汇声明在整个switch中都是可见的,而switch块只有被赋值的时候才能被初始化,赋值发生在case条件符合的时候。当多个case 定义同一个内容时引发问题。
// bad
switch (foo) {
case 1:
let x = 1;
break;
case 2:
const y = 2;
break;
case 3:
function f() {
// ...
}
break;
default:
class C {}
}
// 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.6 三元运算符写在一行。
// 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 避免使用不必要的三元运算。
// 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 使用大括号包裹所有的多行代码块。
// bad
if (test)
return false;
// good
if (test) return false;
// good
if (test) {
return false;
}
// bad
function() { return false; }
// good
function() {
return false;
}
16.2 如果通过 if 和 else 使用多行代码块,把 else 放在 if 代码块关闭括号的同一行。
// bad
if (test) {
thing1();
thing2();
}
else {
thing3();
}
// good
if (test) {
thing1();
thing2();
} else {
thing3();
}
注释(Comments)
17.1 使用 /** … */ 作为多行注释。包含描述、指定所有参数和返回值的类型和值。
// bad
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make(tag) {
// ...stuff...
return element;
}
// good
/**
* make() returns a new element
* based on the passed in tag name
*
* @param {String} tag
* @return {Element} element
*/
function make(tag) {
// ...stuff...
return element;
}
17.2 使用 // 作为单行注释。在评论对象上面另起一行使用单行注释。在注释前插入空行。
// 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;
}
17.3在所有的注释内容前(双斜杠之后)加入空格,创造易读性。
// bad
//is current tab
const active = true;
// good
// is current tab
const 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;
}
17.4 给注释增加 FIXME 或 TODO 的前缀可以帮助其他开发者快速了解这是一个需要复查的问题,或是给需要实现的功能提供一个解决方式。这将有别于常见的注释,因为它们是可操作的。
17.5 使用 // FIXME: 标注问题。
class Calculator {
constructor() {
// FIXME: shouldn't use a global here
total = 0;
}
}
17.6 使用 // TODO: 标注问题的解决方式。
class Calculator {
constructor() {
// TODO: total should be configurable by an options param
this.total = 0;
}
}
空白
18.1 使用 4 个空格作为缩进。
// bad
function() {
∙const name;
}
// bad
function() {
∙∙const name;
}
// good
function() {
∙∙∙∙const name;
}
18.2 在花括号前放一个空格。
// bad
function test(){
console.log('test');
}
// good
function test() {
console.log('test');
}
// bad
dog.set('attr',{
age: '1 year',
breed: 'Bernese Mountain Dog',
});
// good
dog.set('attr', {
age: '1 year',
breed: 'Bernese Mountain Dog',
});
18.3 在控制语句(if、while 等)的小括号前放一个空格。在函数调用及声明中,不在函数的参数列表前加空格。
// bad
if(isJedi) {
fight ();
}
// good
if (isJedi) {
fight();
}
// bad
function fight () {
console.log ('Swooosh!');
}
// good
function fight() {
console.log('Swooosh!');
}
18.4 使用空格把运算符隔开。
// bad
const x=y+5;
// good
const x = y + 5;
18.5 在文件末尾插入一个空行。
// bad
import { es6 } from './AirbnbStyleGuide';
// ...
export default es6;
```
```javascript
// bad
import { es6 } from './AirbnbStyleGuide';
// ...
export default es6;↵
↵
```
```javascript
// good
import { es6 } from './AirbnbStyleGuide';
// ...
export default es6;↵
18.6 在使用长方法链时进行缩进。使用前面的点 . 强调这是方法调用而不是新语句。
// 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').class('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);
18.7 在块末和新语句前插入空行。
// bad
if (foo) {
return bar;
}
return baz;
// good
if (foo) {
return bar;
}
return baz;
// bad
const obj = {
foo() {
},
bar() {
},
};
return obj;
// good
const obj = {
foo() {
},
bar() {
},
};
return obj;
// bad
const arr = [
function foo() {
},
function bar() {
},
];
return arr;
// good
const arr = [
function foo() {
},
function bar() {
},
];
return arr;
18.8 不要在块内的首尾位置使用空行。
// bad
function bar() {
console.log(foo);
}
// also bad
if (baz) {
console.log(qux);
} else {
console.log(foo);
}
// good
function bar() {
console.log(foo);
}
// good
if (baz) {
console.log(qux);
} else {
console.log(foo);
}
18.9 不要在圆括号内首尾增加空格。
// bad
function bar( foo ) {
return foo;
}
// good
function bar(foo) {
return foo;
}
// bad
if ( foo ) {
console.log(foo);
}
// good
if (foo) {
console.log(foo);
}
18.10不要在方括号内首尾增加空格。
// bad
const foo = [ 1, 2, 3 ];
console.log(foo[ 0 ]);
// good
const foo = [1, 2, 3];
console.log(foo[0]);
18.11 大括号里需要增加空格。
// bad
const foo = {clark: 'kent'};
// good
const foo = { clark: 'kent' };
18.12 每行代码避免超过100个字符(包括空格)。注意:如果超过了需要使用链接符进行截断,具体见示例。
增强可读性和可维护性。
// bad
const 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.'));
// good
const 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.'));
逗号(Commas)
19.1 不要在行首添加逗号。
// 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',
};
19.2 增加结尾的逗号: 需要。
为什么? 这会让 git diffs 更干净。另外,像 babel 这样的转译器会移除结尾多余的逗号,也就是说你不必担心老旧浏览器的尾逗号问题。
// bad - git diff without trailing comma
const hero = {
firstName: 'Florence',
- lastName: 'Nightingale'
+ lastName: 'Nightingale',
+ inventorOf: ['coxcomb graph', 'modern nursing']
}
// good - git diff with trailing comma
const hero = {
firstName: 'Florence',
lastName: 'Nightingale',
+ inventorOf: ['coxcomb chart', 'modern nursing'],
}
// bad
const hero = {
firstName: 'Dana',
lastName: 'Scully'
};
const heroes = [
'Batman',
'Superman'
];
// good
const hero = {
firstName: 'Dana',
lastName: 'Scully',
};
const heroes = [
'Batman',
'Superman',
];
分号(Semicolons)
20.1 使用分号
// bad
(function() {
const name = 'Skywalker'
return name
})()
// good
(() => {
const name = 'Skywalker';
return name;
})();
// good (防止函数在两个 IIFE 合并时被当成一个参数)
;(() => {
const name = 'Skywalker';
return name;
})();
类型转换(Type Casting & Coercion)
21.1 在语句开始时执行类型转换。
21.2 字符串:
// => this.reviewScore = 9;
// bad
const totalScore = this.reviewScore + '';
// good
const totalScore = String(this.reviewScore);
21.3 对数字使用 parseInt 转换,并带上类型转换的基数。
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);
21.4 如果因为某些原因 parseInt 成为你所做的事的瓶颈而需要使用位操作解决性能问题时,留个注释说清楚原因和你的目的。
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
// good
/**
* 使用 parseInt 导致我的程序变慢,
* 改成使用位操作转换数字快多了。
*/
const val = inputValue >> 0;
21.5 注: 小心使用位操作运算符。数字会被当成 64 位值,但是位操作运算符总是返回 32 位的整数(参考)。位操作处理大于 32 位的整数值时还会导致意料之外的行为。关于这个问题的讨论。最大的 32 位整数是 2,147,483,647:
2147483647 >> 0 //=> 2147483647
2147483648 >> 0 //=> -2147483648
2147483649 >> 0 //=> -2147483647
21.6 布尔:
const age = 0;
// bad
const hasAge = new Boolean(age);
// good
const hasAge = Boolean(age);
// good
const hasAge = !!age;
命名规则(Naming Conventions)
22.1 避免单字母命名。命名应具备描述性。
// bad
function q() {
// ...stuff...
}
// good
function query() {
// ..stuff..
}
22.2 使用驼峰式命名对象、函数和实例。
// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}
// good
const thisIsMyObject = {};
function thisIsMyFunction() {}
22.2 使用驼峰式命名对象、函数和实例。
// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}
// good
const thisIsMyObject = {};
function thisIsMyFunction() {}
22.3 使用帕斯卡式命名构造函数或类。
// bad
function user(options) {
this.name = options.name;
}
const bad = new user({
name: 'nope',
});
// good
class User {
constructor(options) {
this.name = options.name;
}
}
const good = new User({
name: 'yup',
});
22.4 使用下划线 _ 开头命名私有属性。
为什么呢?JavaScript原本没有私有属性或者私有方法的概念,尽管在变量前面增加下划线可以表达“私有”的含义,但变量事实上仍然是公有的。这样的约束会帮助开发者减少这样的误区。
// bad
this.__firstName__ = 'Panda';
this._firstName = 'Panda';
this.firstName_ = 'Panda';
// good
this.firstName = 'Panda';
22.5 别保存 this 的引用。
// 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);
};
}
22.6 如果你的文件只输出一个类,那你的文件名必须和类名完全保持一致。
// file contents
class CheckBox {
// ...
}
export default CheckBox;
// in some other file
// bad
import CheckBox from './checkBox';
// bad
import CheckBox from './check_box';
// good
import CheckBox from './CheckBox';
22.7 当你导出默认的函数时使用驼峰式命名。你的文件名必须和函数名完全保持一致。
function makeStyleGuide() {
}
export default makeStyleGuide;
22.8 当你导出单例、函数库、空对象时使用帕斯卡式命名。
const AirbnbStyleGuide = {
es6: {
}
};
export default AirbnbStyleGuide;
22.9 首字母缩略词或者国际化名词应保持全部大写或者小写。
原因:这样为了保持良好的代码可读性,区别与一个普通的程序算法。
```javascript
// bad
import SmsContainer from './containers/SmsContainer';
// bad
const HttpRequests = [
// ...
];
// good
import SMSContainer from './containers/SMSContainer';
// good
const HTTPRequests = [
// ...
];
// best
import TextMessageContainer from './containers/TextMessageContainer';
// best
const Requests = [
// ...
];
存取器(Accessors)
23.1 属性的存取函数不是必须的。
23.2 如果你需要存取函数时使用getVal() 及 setVal(‘hello’)。
// bad
dragon.age();
// good
dragon.getAge();
// bad
dragon.age(25);
// good
dragon.setAge(25);
23.3 如果属性是布尔值,使用 isVal() 或 hasVal()。
// bad
if (!dragon.age()) {
return false;
}
// good
if (!dragon.hasAge()) {
return false;
}
23.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];
}
}
事件(Events)
24.1 当给事件附加数据时(无论是 DOM 事件还是私有事件),传入一个哈希而不是原始值。这样可以让后面的贡献者增加更多数据到事件数据而无需找出并更新事件的每一个处理器。例如,不好的写法:
// bad
$(this).trigger('listingUpdated', listing.id);
...
$(this).on('listingUpdated', function(e, listingId) {
// do something with listingId
});
更好的写法:
// good
$(this).trigger('listingUpdated', { listingId : listing.id });
...
$(this).on('listingUpdated', function(e, data) {
// do something with data.listingId
});
jQuery
25.1 使用 $ 作为存储 jQuery 对象的变量名前缀。
// bad
const sidebar = $('.sidebar');
// good
const $sidebar = $('.sidebar');
25.2 缓存 jQuery 查询。
// bad
function setSidebar() {
$('.sidebar').hide();
// ...stuff...
$('.sidebar').css({
'background-color': 'pink'
});
}
// good
function setSidebar() {
const $sidebar = $('.sidebar');
$sidebar.hide();
// ...stuff...
$sidebar.css({
'background-color': 'pink'
});
}
25.3 对 DOM 查询使用层叠 $(‘.sidebar ul’) 或 父元素 > 子元素 $(‘.sidebar > ul’)。
25.4 对有作用域的 jQuery 对象查询使用 find。
// bad
$('ul', '.sidebar').hide();
// bad
$('.sidebar').find('ul').hide();
// good
$('.sidebar ul').hide();
// good
$('.sidebar > ul').hide();
// good
$sidebar.find('ul').hide();