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();
console.log(animal.hasOwnProperty('toString')); console.log(animal.__proto__hasOwnProperty('toString'));
|
2、模块化(Module)
模块化的功能主要是由 export 和 import组成,每个模块有自己的单独的作用域,通过export对外暴露接口,通过import引用其他模块提供的接口,同时各个模块有自己单独的命名空间,减少了命名冲突。
注意: 导入和导出的写法必须配套,不能混用!
export
export 导出多个变量或者函数(以在 React 及 ReactNative 为例)
1 2 3 4 5
| var MyComponent = React.createClass({ ... }); module.exports = MyComponent;
|
1 2 3 4
| 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
| var React = require("react"); var { Component, PropTypes, } = React;
var ReactNative = require("react-native"); var { Image, Text, } = ReactNative;
|
1 2 3 4 5 6
| 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) => { };
|
4、函数参数默认值
1 2 3 4 5 6 7 8 9 10 11
| function defaultValue(a = 1, b = '0') {
}
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); console.log(two); console.log(three);
let {one, two, three, , five} = foo;
var a, b; [a, b] = foo; console.log(a); console.log(b);
var c, d; [c=100, d=200] = [10]; console.log(c); console.log(d);
var a = 1, b = 2; [a, b] = [b, a]; console.log(a); console.log(b);
|
对象取值
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); console.log(age); console.log(sex); ```
### 7、延展操作符(Spread operator)
**...** 在函数调用、数组构造时,将 String 或者 表达式在语法层面展开,也可以在构造对象时,将对象表达式按key-value的方式展开。
```javascript
func(...iterableObj) { }
[...iterableObj, 'aa', ...'Monetking', 23, 57];
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));
console.log(sum.apply(null, numbers));
const students = ['Monetking', 'lisi']; const allStudents ['Tom', '张三', ...students, 'wangwu', 'zhouqi']; console.log(allStudents);
let arr = [1, 2, 3, 4]; var arr2 = [...arr]; arr2.push(5)l console.log(arr2);
let arr1 = [0, 1, 2]; let arr2 = [3, 4, 5]; let arr3 = [...arr2, ...arr2];
let arr4 = arr1.concat(arr2);
let obj1 = {aa: 11, bb: 22}; let obj2 = {cc: 33, bb: 44}
let cloneObj = { ...obj1 }; let mergeObj = { ...obj1, ...obj2 };
let params = {name: 11, age: 22, sex: 1};
var {name, ...other} = params;
restParam(1, 2, 3, 4, 5);
function restParam(p1, p2, ...p3) { }
|
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);
|
ES6
1 2 3 4 5 6 7 8
| const name = 'Monetking', age = '18', sex = 1; let person = { name, age, sex };
console.log(person);
|
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);
|
let和const定义的变量为块级作用域:
1 2 3 4
| { let aa = 100; } console.log(aa);
|
ES7特性
常用特性
1、Array.prototype.includes()
includes() 函数用来判断一个数组里是否包含指定的值,返回值为布尔值。这个函数的功能和 indexOf() 类似。
1 2 3 4 5 6 7 8 9 10 11
| let aa = [11, 22, 33, 44];
if (aa.indexOf(11) !== -1) { console.log('存在'); }
if (aa.includes(11)) { console.log('存在'); }
|
2、指数操作符 **
指数操作符 ** 和 Math.pow() 作用相同
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); }); }
func1() { this.step1() .then(this.step2()) .then(result => { console.log(result); }) }
async func2() { const aa = await this.step1(); const bb = await this.step2(); const [aa, bb] = await Promise.all([this.step1(), this.step2()]); }
function doSomething() { doSomething1() .then(doSomething2) .then(doSomething3) .catch(err => { console.log(err); }) .finally(() => { }); }
this.func1(); this.func2(); ```
**常见的捕捉错误的写法**
一、捕捉整个 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)); const b = await this.funcCommon(y) .catch(error => console.log(error)); 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};
const value1 = Object.keys(obj).map(key=>objp[key]); console.log(value1);
const value2 = Object.values(obj); console.log(value2);
|
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]}`) })
for(let [key, value] of Object.entries(obj)) { console.log(`key=${key} value=${obj[key]}`) }
|
4、String padding
新增两个函数:padStart 和 padEnd,允许将空字符串或者其他字符串添加到开头或者结尾
语法:String.padStart(targetLength, [padString])
语法:String.padEnd(targetLength, [padString])
targetLength: 当前字符串需要填充到的目标长度,如果小于当前字符串长度,返回当前字符串本身。
padString: (可选)填充字符串,如果字符串太长,使填充后的字符串长度超过了目标长度,只保留最左侧的部分,其他部分会被截断,该参数的缺省值为空字符换” “
1 2
| console.log("0.0".padStart(4, '10')); console.log("0.0".padStart(4));
|
1 2
| console.log("0.0".padEnd(4, '0')); console.log("0.0".padStart(10,'0'));
|
5、Object.getOwnPropertyDescriptors()
返回对象自身属性的描述符,如果没有任何自身属性,返回空。
6、函数参数列表结尾允许逗号
主要解决多人协作开发中,代码冲突问题.
ES8之前
1 2 3 4 5 6 7 8 9 10 11 12
| var func1 = function(a, 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
| var func1 = function(a, b, ) { }
var func1 = function(a, b, 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) { }
restParam(1, 2, 3, 4, 5);
|
1 2 3 4 5 6 7 8 9 10 11
| const myObject = { a: 1, b: 2, c: 3 };
const { a, ...x } = myObject;
|
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(() => { }); }
|
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], month = match[2], day = match[3];
|
这样的代码很难读懂,并且改变正则表达式的结构有可能改变匹配对象的索引。
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, month = match.groups.month, day = match.groups.day;
|
任何匹配失败的命名组都将返回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] );
|
以上是 肯定反向断言,非数字\D必须存在。同样的,还存在 否定反向断言,表示一个值必须不存在,例如:
1 2 3 4 5
| const reLookbehindNeg = /(?<!\D)\d+/, match = reLookbehind.exec('$123.89');
console.log( match[0] );
|
6、正则表达式dotAll模式
正则表达式中点.匹配除回车外的任何单字符,标记s改变这种行为,允许行终止符的出现,例如:
1 2
| /hello.world/.test('hello\nworld'); /hello.world/s.test('hello\nworld');
|
7、正则表达式 Unicode 转义
到目前为止,在正则表达式中本地访问 Unicode 字符属性是不被允许的。ES2018添加了 Unicode 属性转义——形式为\p{…}和\P{…},在正则表达式中使用标记 u (unicode) 设置,在\p块儿内,可以以键值对的方式设置需要匹配的属性而非具体内容。例如:
1 2
| const reGreekSymbol = /\p{Script=Greek}/u; reGreekSymbol.test('π');
|
此特性可以避免使用特定 Unicode 区间来进行内容类型判断,提升可读性和可维护性。
8、非转义序列的模板字符串
ES2018 移除对 ECMAScript 在带标签的模版字符串中转义序列的语法限制。
之前,\u开始一个 unicode 转义,\x开始一个十六进制转义,\后跟一个数字开始一个八进制转义
参考链接: