ES6/ES7参考手册

前言

本文档记录学习ECMAScript 6过程中的各个方面内容,包含了一些ES6/7的新语法和一些编码建议,较零散,作参考。

1.对象

1.1 使用字面量创建对象

1
2
3
4
5
// bad
const item = new Object();
// good
const item = {};

1.2 对象属性简写

ES6推荐使用对象属性简写,支持对象的属性和方法。

为什么?因为这样更短更有描述性。

  • 下面代码表明,ES6允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。
1
2
3
4
5
6
var foo = 'bar';
var baz = {foo};
baz // {foo: "bar"}
// 等同于
var baz = {foo: foo};
  • 方法也可以简写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// bad 原始写法
const atom = {
value: 1,
addValue: function (value) {
return atom.value + value;
},
};
// good 简写
const atom = {
value: 1,
addValue(value) {
return atom.value + value;
},
};
  • 用于返回值时使用简写,特别简洁:
1
2
3
4
5
6
7
8
function getPoint() {
var x = 1;
var y = 10;
return {x, y}; // 这里是简写返回值
}
getPoint()
// {x:1, y:10}
  • 在对象属性声明前把简写的属性分组

为什么?因为这样能清楚地看出哪些属性使用了简写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
episodeOne: 1,
twoJedisWalkIntoACantina: 2,
lukeSkywalker,
episodeThree: 3,
mayTheFourth: 4,
anakinSkywalker,
};
// good
const obj = {
lukeSkywalker,
anakinSkywalker,
episodeOne: 1,
twoJedisWalkIntoACantina: 2,
episodeThree: 3,
mayTheFourth: 4,
};

2.数组

2.1 使用字面量创建数组

1
2
3
4
5
// bad
const items = new Array();
// good
const items = [];

2.2 使用push代替直接赋值

1
2
3
4
5
6
7
const someStack = [];
// bad
someStack[someStack.length] = 'abracadabra';
// good
someStack.push('abracadabra');

2.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++) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];

### 2.4 使用 Array#from 把一个类数组对象转换成数组。

1
2
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);

2.5 对象解构赋值

  • 数组可以
1
2
3
4
5
6
7
8
9
10
11
var [a, b, c] = [1, 2, 3];
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
// 解构的默认值
[x, y = 'b'] = ['a']; // x='a', y='b'
[x, y = 'b'] = ['a', undefined]; // x='a', y='b'
[x, y = 'b'] = ['a', null]; // x='a', y=null
  • 对象可以
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
var { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined
// 如果变量名不一致,需要指定别名
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'
first // error: foo is not defined
// 解构的默认值
var {x, y = 5} = {x: 1};
x // 1
y // 5

注意,对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值

默认值生效的条件是,对象的属性值严格等于undefined。如果解构不成功,变量的值就等于undefined

3.字符串

3.1 使用单引号''

1
2
3
4
5
6
7
8
9
10
11
// bad
var name = "Bob Parr";
// good
var name = 'Bob Parr';
// bad
var fullName = "Bob " + this.lastName;
// good
var fullName = 'Bob ' + this.lastName;

3.2 使用模板连接字符串

1
2
3
`${name} is a man` // good 字符串模板优先
['How are you, ', name, '?'].join() // bad 其次是join
'How are you, ' + name + '?' // bad 最后才是连接符

4.函数

4.1 函数声明优先

为什么?因为函数声明是可命名的,所以他们在调用栈中更容易被识别。此外,函数声明会把整个函数提升(hoisted),而函数表达式只会把函数的引用变量名提升。这条规则使得箭头函数可以取代函数表达式。

1
2
3
4
5
6
7
// bad
const foo = function () {
};
// good
function foo() {
}

4.2 函数参数

直接给函数的参数指定默认值,不要使用一个变化的函数参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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 = {}) {
// ...
}

推荐,如果有多个参数,请使用参数对象包装,这样在增加参数的时候,不需要改变函数声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// bad
$(this).trigger('listingUpdated', listing.id);
...
$(this).on('listingUpdated', function(e, listingId) {
// do something with listingId
});
// good
$(this).trigger('listingUpdated', { listingId : listing.id });
// good
$(this).on('listingUpdated', function(e, data) {
// do something with data.listingId
});

4.3 箭头函数

  • 当你必须使用函数表达式(或传递一个匿名函数)时,使用箭头函数符号。

为什么?因为箭头函数创造了新的一个 this 执行环境(译注:参考 Arrow functions - JavaScript | MDNES6 arrow functions, syntax and lexical scoping),通常情况下都能满足你的需求,而且这样的写法更为简洁。

为什么不?如果你有一个相当复杂的函数,你或许可以把逻辑部分转移到一个函数声明上。

1
2
3
4
5
6
7
8
9
// bad
[1, 2, 3].map(function (x) {
return x * x;
});
// good
[1, 2, 3].map((x) => {
return x * x;
});
  • 如果一个函数适合用一行写出并且只有一个参数,那就把花括号、圆括号和 return 都省略掉。如果不是,那就不要省略。

为什么?语法糖。在链式调用中可读性很高。

为什么不?当你打算回传一个对象的时候。

1
2
3
4
5
6
7
// good
[1, 2, 3].map(x => x * x);
// good
[1, 2, 3].reduce((total, n) => {
return total + n;
}, 0);

4.4 函数参数默认值

在4.2中知道,函数的参数现在允许使用默认值了,写法就是(a = value)

1
2
3
4
5
6
7
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

注意,参数的默认值顺序,最好写在末尾参数,即使非末尾参数指定了默认值,在使用时也不能省略该参数。

1
2
3
4
5
6
7
8
9
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined])
f(, 1) // 报错
f(undefined, 1) // [1, 1]/// 注意这里,和null的区别
f(null, 1) // [null, 1] /// 注意这里,和undefined的区别

如果非尾参数指定了默认值,除非显示的传入undefined,否则将无法触发参数的默认值。null 值则仍未null值。

注意,如果给参数指定了默认值,函数的length属性则为预期需要传入的参数个数,如果非尾参数,则会忽略计算后面的参数。

1
2
3
(function (a=1, b, c) {}).length // 0
(function (a, b=1, c) {}).length // 1
(function (a, b, c=5) {}).length // 2

4.5 函数参数的解构赋值

在之前的章节中,介绍了对象的解构赋值,同样,函数的参数(包括构造函数)也可以解构。

1
2
3
4
5
6
7
8
function foo({x, y = 5}) {
console.log(x, y);
}
foo({}) // undefined, 5
foo({x: 1}) // 1, 5
foo({x: 1, y: 2}) // 1, 2
foo() // TypeError: Cannot read property 'x' of undefined

利用这个特性,我们可以写出非常简洁的代码:

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
// 这是通用返回结构
class Result {
constructor({ data, code = 200, message = '执行成功' }) {
this.code = code;
this.message = message;
if(!!data) { // null值将被忽略
this.data = data;
}
}
static ok(data) {
return new Result({ data });
}
static error(code, message) {
return new Result( { code, message });
}
}
// success时返回
const data = {user: 'abc'};
const result = Result.ok(data);
ctx.body = result;
// error时返回
const result = Result.error(401, 'Unauthorized');
ctx.body = result;

5.模块

5.1 总是使用import/export

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

5.2 不要使用import通配符

Why? This makes sure you have a single default export.

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

5.3 不要导出可变的对象

Why? Mutation should be avoided in general, but in particular when exporting mutable bindings. While this technique may be needed for some special cases, in general, only constant references should be exported.

eslint: import/no-mutable-exports

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

5.4 如果只有一个导出,使用default.

eslint: import/prefer-default-export

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

6.并发

6.1 async and await

下面的代码是koa2中的router filter,我们利用async和await,以及Promise,同时执行了两个数据库查询,等待执行完毕后,返回结果。

1
2
3
4
5
6
7
8
9
// User.findById 和Channel.findOne将被同时触发
async me(ctx, next) {
const _me = ctx.state.user;
const[user, channel] = await Promise.all([User.findById(_me.id), Channel.findOne()]);
ctx.body = {
user,
channel,
};
},

代码风格

使用eslint: eslint-config-airbnb-base

逗号

  • 行首逗号:不需要。eslint: comma-style jscs: requireCommaBeforeLineBreak

    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',
    };
  • 增加结尾的逗号: 需要

    为什么? 这会让 git diffs 更干净。另外,像 babel 这样的转译器会移除结尾多余的逗号,也就是说你不必担心老旧浏览器的尾逗号问题

    eslint: comma-dangle jscs: requireTrailingComma

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // bad - git diff without trailing comma
    const hero = {
    firstName: 'Florence',
    - lastName: 'Nightingale'
    + lastName: 'Nightingale',
    + inventorOf: ['coxcomb chart', 'modern nursing']
    };
    // good - git diff with trailing comma
    const hero = {
    firstName: 'Florence',
    lastName: 'Nightingale',
    + inventorOf: ['coxcomb chart', 'modern nursing'],
    };

分号

使用分号

eslint: semi jscs: requireSemicolons

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// bad
(function() {
const name = 'Skywalker'
return name
})()
// good
(() => {
const name = 'Skywalker';
return name;
})();
// good (防止函数在两个 IIFE 合并时被当成一个参数)
;(() => {
const name = 'Skywalker';
return name;
})();

Babel

示例:.babelrc

1
2
3
4
5
6
7
{
"presets": ["es2015", "flow-vue"],
"ignore": [
"dist/*.js",
"packages/**/*.js"
]
}

presets字段设定转码规则,官方提供以下的规则集presets

关于ES7不同阶段的提案, stage-0 > stage-1 > stage-2 > stage-3

参考: