logo头像
Snippet 博客主题

ES6、ES7、ES8、ES9 常用特性总结

本文于 351 天之前发表,文中内容可能已经过时。

概述

由 Brendan Eich 发明,第一次出现是在网景的 Navigator 2.0浏览器。

ECMAScript 简称 特性 发布时间
ECMAScript2009 ES5 扩展Object、Array、Function等功能 2009年11月
ECMAScript2015 ES6 增加类、模块化、箭头函数、函数参数默认值等 2015年6月
ECMAScript2016 ES7 增加includes、指数操作符等 2016年3月
ECMAScript2017 ES8 sync/await、Object.values()、Object.entries()、String padding等 2015年6月
ECMAScript2018 ES9 延展操作符增加对对象的支持、异步迭代器、Promise.finally()、正则表达式命名捕获组(Regular Expression Named Capture Groups)、正则表达式反向断言(lookbehind)、正则表达式dotAll模式、正则表达式 Unicode 转义、非转义序列的模板字符串 2018年6月


ES6 特性

常用特性

  • 模块化
  • 箭头函数
  • 函数参数默认值
  • 模板字符串
  • 解构赋值
  • 延展字符串
  • 对象属性简写
  • Promise
  • Let 与 Const

1、类 Class

使 JavaScript 面向对象编程更加简单、易于理解和Coding。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Animal {
// 构造函数,实例化时被调用,如果不写,将会有默认是不带参数的
constructor(type, color) {
this.type = type;
this.color = color;
}

toString() {
console.log("type=" + this.type + ", color=" + this.color);
}
}

// 实例化
let animal = new Animal('cat', 'red');
animal.toString();

// toString 是原型对象的属性
console.log(animal.hasOwnProperty('toString')); // false
console.log(animal.__proto__hasOwnProperty('toString')); // true

2、模块化(Module)

模块化的功能主要是由 export 和 import组成,每个模块有自己的单独的作用域,通过export对外暴露接口,通过import引用其他模块提供的接口,同时各个模块有自己单独的命名空间,减少了命名冲突。

注意: 导入和导出的写法必须配套,不能混用!

export

export 导出多个变量或者函数(以在 React 及 ReactNative 为例)

1
2
3
4
5
//ES5
var MyComponent = React.createClass({
...
});
module.exports = MyComponent;
1
2
3
4
//ES6
export default class MyComponent extends Component{
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 导出变量
export var name = '123';
let a = '1';
let b = '2';
export {a, b};

// 导出常量
export const sqrt = Math.sqrt();

// 导出方法
export function exportFunc() {

}

import

通过 import 导入已经导出的模块(以在 React 及 ReactNative 为例)

1
2
3
4
5
6
// ES5
var React = require("react");
var { Component, PropTypes, } = React; //引用React抽象组件

var ReactNative = require("react-native");
var { Image, Text, } = ReactNative; //引用具体的React Native组件
1
2
3
4
5
6
// ES6
import React, { Component, PropTypes, } from 'react';
import { Image, Text } from 'react-native'

// 可以导入 默认函数 和 其他函数
import defaultMethod { otherMethod} from 'b.js';

3、箭头函数

箭头函数是 ES6 中最令人激动的特性之一,其中 => 不仅是function的简写,而且还有和包裹的内容共享this,解决this指向的问题。

书写格式:

  • 空括号: () => {}
  • 单个参数名: a => {}
  • 多个参数名: (a, b, c) => {}

其中 => 之后可以是一个表达式,或者是一个花括号包裹的函数体,需要手动处理 return,否则返回 undefined

  • (a, b) => a + b;
  • (a, b) => { return a + b};

注意:无论是箭头函数还是bind,每次被执行都会返回一个新的函数引用,因此还需要函数引用去做一些额外的事情(比如: 卸载监听器),那么必须自己保存这个引用

卸载监听器的正确做法:

错误写法:
这样每次bind都会返回一个新的函数引用,导致移除的不是添加的,移除失败.

1
2
3
4
5
6
7
8
9
10
11
12
// 监听器错误写法
componentWillMount() {
DeviceEventEmitter.addListener('key', this._onItemClick.bind(this));
}

componentWillUnmount() {
DeviceEventEmitter.removeListener('key', this._onItemClick.bind(this));
}

_onItemClick(event) {

}

正确写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 构造器
constructor(props) {
super(props);
this.state = {
};
this._onItemClick = this._onItemClick.bind(this);
}

componentWillMount() {
DeviceEventEmitter.addListener('key', this._onItemClick);
}

componentWillUnmount() {
DeviceEventEmitter.removeListener('key', this._onItemClick);
}

_onItemClick(event) {

}

箭头函数的属性写法:

1
2
3
4
5
6
7
8
9
10
11
componentWillMount() {
DeviceEventEmitter.addListener('key', this._onItemClick);
}

componentWillUnmount() {
DeviceEventEmitter.removeListener('key', this._onItemClick);
}

_onItemClick = (event) => {
// 把函数直接作为一个arrow function的属性定义,初始化的时候绑定好this指针
};

4、函数参数默认值

1
2
3
4
5
6
7
8
9
10
11
function defaultValue(a = 1, b = '0') {

}

// 不使用默认值写法
// ⚠️这样写法不推荐,当a真的是0时,aa的结果会是1,与结果不符。
function noDefaultValue(a, b) {

let aa = a || 1;
let bb = b || '0';
}

5、模板字符串

字符串拼接更加方便、简洁、直观。

1
2
3
4
5
// 使用模板字符串
let newName1 = `my name is ${aa} ${bb}.`;

// 不使用模板字符串
let newName2 = 'my nname ' + aa + '' + bb + '.';

这样的写法和 Shell 里的写法完全相似,注意不是单引号,而是 ESC 下面的那个按键符号 ``.

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
let foo = ["00", "11", "22", "33", "44"];

let {one, two, three} = foo;
console.log(one); // "00"
console.log(two); // "11"
console.log(three); // "22"

// 如果想忽略某个值可以这样写
let {one, two, three, , five} = foo;

// 另一种写法
var a, b; // 先声明
[a, b] = foo;
console.log(a); // "00"
console.log(b); // "11"

// 如果没有从数组中获取值,可以书写默认值
var c, d;
[c=100, d=200] = [10];
console.log(c); // 10
console.log(d); // 200

// 数值交换
var a = 1, b = 2;
[a, b] = [b, a];
console.log(a); // 2
console.log(b); // 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
const person = {
name: 'Monetking',
age: 20,
sex: '男'
};

const { name, age, sex} = person;
console.log(name); // 'Monetking'
console.log(age); // 20
console.log(sex); // '男'
```

### 7、延展操作符(Spread operator)

**...** 在函数调用、数组构造时,将 String 或者 表达式在语法层面展开,也可以在构造对象时,将对象表达式按key-value的方式展开。

```javascript
// 函数调用
func(...iterableObj) {
}

// 数组构造或者字符串

[...iterableObj, 'aa', ...'Monetking', 23, 57];

// 构造对象时,进行克隆或者属性拷贝(ES9)
let objClone = { ...obj };

应用

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
55
56
// ========== 函数调用 ============
function sum(a, b, c) {
return a + b +c;
}

const numbers = [10, 20, 30];

// 使用延展操作符
console.log(sum(...numbers)); // 60

// 不使用延展操作符
console.log(sum.apply(null, numbers)); // 60


// ========= 构造函数 ============
// 替代了原来的push、splice、concat等方法
const students = ['Monetking', 'lisi'];
const allStudents ['Tom', '张三', ...students, 'wangwu', 'zhouqi'];
console.log(allStudents); // ['Tom', '张三', 'Monetking', 'lisi', 'wangwu', 'zhouqi'];

// ========== 数组拷贝 =========
let arr = [1, 2, 3, 4];
var arr2 = [...arr]; // 等同于 arr.slice()
arr2.push(5)l
console.log(arr2); // [1, 2, 3, 4, 5]
// 和 Object.assign() 效果一致,执行的都是浅拷贝,只遍历一层。


// ======== 连接数组 ========
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
let arr3 = [...arr2, ...arr2];

// 等价于
let arr4 = arr1.concat(arr2);

// ======== ES9对对象的支持 ========
let obj1 = {aa: 11, bb: 22};
let obj2 = {cc: 33, bb: 44}

let cloneObj = { ...obj1 }; // {aa: 11, bb: 22}
let mergeObj = { ...obj1, ...obj2 }; // {aa: 11, bb: 44, cc: 33}

let params = {name: 11, age: 22, sex: 1};

// 结构排除指定值, 其中...ohter就是包含 age 和 sex
var {name, ...other} = params;

// ========= 函数不定参数 ========
restParam(1, 2, 3, 4, 5);

function restParam(p1, p2, ...p3) {
// p1 = 1
// p2 = 2
// p3 = [3, 4, 5]
}

8、对象属性的简写

ES6 中可以设置一个对象的属性的时候不指定属性名

ES6之前

1
2
3
4
5
6
7
8
const name = 'Monetking', age = '18', sex = 1;
let person = {
name: name,
age: age,
sex: sex
};

console.log(person); // {name:'Monetking', age: '18', sex: '1}

ES6

1
2
3
4
5
6
7
8
const name = 'Monetking', age = '18', sex = 1;
let person = {
name,
age,
sex
};

console.log(person); // {name:'Monetking', age: '18', sex: '1}

9、Promise

ES6 中实现了的异步编程的一种解决方案,代替了以往的callbac方式。

举例:异步编程串行化,避免了回调地狱。

ES6之前

1
2
3
4
5
6
7
8
setTimeout(function(){
console.log('=====monetking1=======');

setTimeout(function(){
console.log('=====monetking2======');
setTime
}, 1500);
}, 1500);

ES6

1
2
3
4
5
6
7
8
9
10
11
12
var waitSecond = new Promise(function(resolve, reject){
setTimeout(resolve, 1000);
});

waitSecond
.then(function(){
console.log('=====monetking1=======');
return waitSecond;
})
.then(function(){
console.log('=====monetking2=======');
})

10、let 和 const

之前没有块级作用域之说,ES6提供的 let 和 const 都是块级作用域。

使用var定义的变量为函数级作用域:

1
2
3
4
{
var aa = 100;
}
console.log(aa); // 100

let和const定义的变量为块级作用域:

1
2
3
4
{
let aa = 100;
}
console.log(aa); // -1 or Error "ReferenceError: aa is not defined"


ES7特性

常用特性

  • includes()
  • 指数操作符

1、Array.prototype.includes()

includes() 函数用来判断一个数组里是否包含指定的值,返回值为布尔值。这个函数的功能和 indexOf() 类似。

1
2
3
4
5
6
7
8
9
10
11
let aa = [11, 22, 33, 44];

//ES7之前
if (aa.indexOf(11) !== -1) {
console.log('存在');
}

//ES7
if (aa.includes(11)) {
console.log('存在');
}

2、指数操作符 **

指数操作符 ** 和 Math.pow() 作用相同

1
console.log(2**3); // 8


ES8的特性

常用特性

  • async/await
  • Object.values()
  • Object.entries()
  • String padding
  • Object.getOwnPropertyDescriptors()
  • 函数参数列表结尾允许逗号

1、async/await

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
step1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
}

step2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Success');
}, 1000);
});
}

// 不使用 async/await
func1() {
this.step1()
.then(this.step2())
.then(result => {
console.log(result);
})
}

// 使用 async/await

async func2() {
const aa = await this.step1();
const bb = await this.step2();

// 并发执行 .all()
const [aa, bb] = await Promise.all([this.step1(), this.step2()]);
}

// 指定最终逻辑 .finally()
function doSomething() {
doSomething1()
.then(doSomething2)
.then(doSomething3)
.catch(err => {
console.log(err);
})
.finally(() => {
// finish here!
});
}

this.func1(); // Success
this.func2(); // Success
```

**常见的捕捉错误的写法**

一、捕捉整个 async/await 的错误

```javascript
async func1(x, y) {
const a = await this.funcCommon(x);
const b = await this.funcCommon(y);
return a + b;
}

test() {
this.func1('1', '2')
.then(console.log)
.catch(console.log); // 捕捉整个函数的错误
}

二、捕捉单个 await 函数的错误

1
2
3
4
5
6
7
8
9
10
11
12
async func1(x, y) {
const a = await this.funcCommon(x)
.catch(error => console.log(error)); // 捕捉单个await 函数的错误
const b = await this.funcCommon(y)
.catch(error => console.log(error)); // 捕捉单个await 函数的错误
return a + b;
}

test() {
this.func1('1', '2')
.then(console.log);
}

三、同时捕捉多个异常错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async func1(x, y) {
let a, b;
try {
a = await this.funcCommon(x);
b = await this.funcCommon(y);
} catch (e) {
console.log(e);
}
return a + b;
}

test() {
this.func1('1', '2')
.then(console.log);
}

2、Object.values()

返回的结果是自身属性的所有值,并不包括继承的值。

1
2
3
4
5
6
7
8
const obj = {a: 1, b: 2, c: 3, d: 4};
// ES8 之前
const value1 = Object.keys(obj).map(key=>objp[key]);
console.log(value1); // [1, 2, 3, 4]

//ES8
const value2 = Object.values(obj);
console.log(value2); // [1, 2, 3, 4]

3、Object.entries()

返回一个对象的自身可枚举属性的键值对数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const obj = {a: 1, b: 2, c: 3, d: 4};

// 不使用
Object.key(obj).forEach(key=>{
console.log(`key=${key} value=${obj[key]}`)
})

//key=a value=1
//key=b value=2
//key=c value=3
//key=d value=4

// 使用

for(let [key, value] of Object.entries(obj)) {
console.log(`key=${key} value=${obj[key]}`)
}

//key=a value=1
//key=b value=2
//key=c value=3
//key=d value=4

4、String padding

新增两个函数:padStart 和 padEnd,允许将空字符串或者其他字符串添加到开头或者结尾

语法:String.padStart(targetLength, [padString])
语法:String.padEnd(targetLength, [padString])

targetLength: 当前字符串需要填充到的目标长度,如果小于当前字符串长度,返回当前字符串本身。
padString: (可选)填充字符串,如果字符串太长,使填充后的字符串长度超过了目标长度,只保留最左侧的部分,其他部分会被截断,该参数的缺省值为空字符换” “

1
2
console.log("0.0".padStart(4, '10')); // 10.0
console.log("0.0".padStart(4)); // 0.00
1
2
console.log("0.0".padEnd(4, '0'));        // 0.00
console.log("0.0".padStart(10'0')); // 0.0000000

5、Object.getOwnPropertyDescriptors()

返回对象自身属性的描述符,如果没有任何自身属性,返回空。

6、函数参数列表结尾允许逗号

主要解决多人协作开发中,代码冲突问题.

ES8之前

1
2
3
4
5
6
7
8
9
10
11
12
// A
var func1 = function(a,
b
) {
}

// B
var func1 = function(a,
b, // 修改行
c // 修改行
) {
}

ES8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// A
var func1 = function(a,
b,
) {
}

// B
var func1 = function(a,
b,
c, // 修改行
) {
}

// C
var func1 = function(a,
b,
c,
d, // 修改行
) {
}


ES9 特性

常用特性

  • 延展操作符增加对对象的支持
  • 异步迭代器
  • Promise.finally()
  • 正则表达式命名捕获组(Regular Expression Named Capture Groups)
  • 正则表达式反向断言(lookbehind)
  • 正则表达式dotAll模式
  • 正则表达式 Unicode 转义
  • 非转义序列的模板字符串

1、延展操作符增加对对象的支持

ES6 引入Rest参数扩展运算符仅应用于数组,Rest参数语法允许我们将一个不定数量的参数表示为一个数组。如果一个对象A的属性是对象B,那么在克隆后的对象cloneB中,该属性指向对象B

1
2
3
4
5
6
7
function restParam(p1, p2, ...p3) {
// p1 = 1
// p2 = 2
// p3 = [3, 4, 5]
}

restParam(1, 2, 3, 4, 5);
1
2
3
4
5
6
7
8
9
10
11
// ES9 Rest() 对象参数
const myObject = {
a: 1,
b: 2,
c: 3
};

// Rest参数只能在声明的结尾处使用
const { a, ...x } = myObject;
// a = 1
// x = { b: 2, c: 3 }

2、异步迭代器

某些需求中需要同步循环中调用异步函数

1
2
3
4
5
6
7
8
9
10
11
async function process(array) {
for (let i of array) {
await doSomething(i);
}
}

async function process(array) {
array.forEach(async i => {
await doSomething(i);
});
}

以上方法,循环本身保持同步,并在在内部异步函数之前全部调用完成.

ES2018引入异步迭代器(asynchronous iterators),这就像常规迭代器,除了next()方法返回一个Promise。因此await可以和for…of循环一起使用,以串行的方式运行异步操作。例如:

1
2
3
4
5
async function process(array) {
for await (let i of array) {
doSomething(i);
}
}

3、Promise.finally()

.finally()允许你指定最终的逻辑:

1
2
3
4
5
6
7
8
9
10
11
function doSomething() {
doSomething1()
.then(doSomething2)
.then(doSomething3)
.catch(err => {
console.log(err);
})
.finally(() => {
// finish here!
});
}

4、正则表达式命名捕获组(Regular Expression Named Capture Groups

JavaScript正则表达式可以返回一个匹配的对象——一个包含匹配字符串的类数组,例如:以YYYY-MM-DD的格式解析日期:

1
2
3
4
5
6
const
reDate = /([0-9]{4})-([0-9]{2})-([0-9]{2})/,
match = reDate.exec('2018-04-30'),
year = match[1], // 2018
month = match[2], // 04
day = match[3]; // 30

这样的代码很难读懂,并且改变正则表达式的结构有可能改变匹配对象的索引。

ES2018允许命名捕获组使用符号?,在打开捕获括号(后立即命名,示例如下:

1
2
3
4
5
6
const
reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/,
match = reDate.exec('2018-04-30'),
year = match.groups.year, // 2018
month = match.groups.month, // 04
day = match.groups.day; // 30

任何匹配失败的命名组都将返回undefined。

命名捕获也可以使用在replace()方法中。例如将日期转换为美国的 MM-DD-YYYY 格式:

1
2
3
4
const
reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/,
d = '2018-04-30',
usDate = d.replace(reDate, '$<month>-$<day>-$<year>');

5、正则表达式反向断言(lookbehind)

目前JavaScript在正则表达式中支持先行断言(lookahead)。这意味着匹配会发生,但不会有任何捕获,并且断言没有包含在整个匹配字段中。例如从价格中捕获货币符号:

1
2
3
4
5
const
reLookahead = /\D(?=\d+)/,
match = reLookahead.exec('$123.89');

console.log( match[0] ); // $

ES2018引入以相同方式工作但是匹配前面的反向断言(lookbehind),这样我就可以忽略货币符号,单纯的捕获价格的数字:

1
2
3
4
5
const
reLookbehind = /(?<=\D)[\d\.]+/,
match = reLookbehind.exec('$123.89');

console.log( match[0] ); // 123.89

以上是 肯定反向断言,非数字\D必须存在。同样的,还存在 否定反向断言,表示一个值必须不存在,例如:

1
2
3
4
5
const
reLookbehindNeg = /(?<!\D)\d+/,
match = reLookbehind.exec('$123.89');

console.log( match[0] ); // null

6、正则表达式dotAll模式

正则表达式中点.匹配除回车外的任何单字符,标记s改变这种行为,允许行终止符的出现,例如:

1
2
/hello.world/.test('hello\nworld');  // false
/hello.world/s.test('hello\nworld'); // true

7、正则表达式 Unicode 转义

到目前为止,在正则表达式中本地访问 Unicode 字符属性是不被允许的。ES2018添加了 Unicode 属性转义——形式为\p{…}和\P{…},在正则表达式中使用标记 u (unicode) 设置,在\p块儿内,可以以键值对的方式设置需要匹配的属性而非具体内容。例如:

1
2
const reGreekSymbol = /\p{Script=Greek}/u;
reGreekSymbol.test('π'); // true

此特性可以避免使用特定 Unicode 区间来进行内容类型判断,提升可读性和可维护性。

8、非转义序列的模板字符串

ES2018 移除对 ECMAScript 在带标签的模版字符串中转义序列的语法限制。

之前,\u开始一个 unicode 转义,\x开始一个十六进制转义,\后跟一个数字开始一个八进制转义



参考链接: