共计 17933 个字符,预计需要花费 45 分钟才能阅读完成。
导言:ECMAScript 的演化不会停止,但是我们完全没必要害怕。除了 ES6 这个史无前例的版本带来了海量的信息和知识点以外,之后每年一发的版本都仅仅带有少量的增量更新,一年更新的东西花半个小时就能搞懂了,完全没必要畏惧。本文将带您花大约一个小时左右的时间,迅速过一遍 ES7,ES8,ES9 的新特性。
想追求更好的阅读体验,请移步原文地址
题记:本文提供了一个在线 PPT 版本,方便您浏览 细解 JAVASCRIPT ES7 ES8 ES9 新特性 在线 PPT ver
本文的大部分内容译自作者 Axel Rauschmayer 博士的网站,想了解更多关于作者的信息,可以浏览 Exploring JS: JavaScript books for programmers
那些与 ECMAScript 有关的事情
谁在设计 ECMAScript?
TC39 (Technical Committee 39)。
TC39 是推进 JavaScript 发展的委员会。其会员都是公司(其中主要是浏览器厂商)。TC39 定期召开会议,会议由会员公司的代表与特邀专家出席。会议纪录都可在网上查看,可以让你对 TC39 如何工作有一个清晰的概念。
很有意思的是,TC39 实行的是协商一致的原则:通过一项决议必须得到每一位会员(公司代表)的赞成。
ECMAScript 的发布周期
在 2015 年发布的 ECMAScript(ES6)新增内容很多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化。两个发布版本之间时间跨度如此之大主要有两大原因:
比新版率先完成的特性,必须等待新版的完成才能发布。
那些需要花长时间完成的特性,也顶着很大的压力被纳入这一版本,因为如果推迟到下一版本发布意味着又要等很久,这种特性也会推迟新的发布版本。
因此,从 ECMAScript 2016(ES7)开始,版本发布变得更加频繁,每年发布一个新版本,这么一来新增内容也会更小。新版本将会包含每年截止时间之前完成的所有特性。
ECMAScript 的发布流程
每个 ECMAScript 特性的建议将会从阶段 0 开始,然后经过下列几个成熟阶段。其中从一个阶段到下一个阶段必须经过 TC39 的批准。
stage-0 – Strawman: just an idea, possible Babel plugin. 任何讨论、想法、改变或者还没加到提案的特性都在这个阶段。只有 TC39 成员可以提交。当前的 stage 0 列表可以查看这里 –> Stage 0 Proposals
stage-1 – Proposal: this is worth working on. 什么是 Proposal?一份新特性的正式建议文档。提案必须指明此建议的潜在问题,例如与其他特性之间的关联,实现难点等。
stage-2 – Draft: initial spec. 什么是 Draft?草案是规范的第一个版本。其与最终标准中包含的特性不会有太大差别。
草案之后,原则上只接受增量修改。这个阶段开始实验如何实现,实现形式包括 polyfill, 实现引擎(提供草案执行本地支持),或者编译转换(例如 babel)
stage-3 – Candidate: complete spec and initial browser implementations. 候选阶段,获得具体实现和用户的反馈。此后,只有在实现和使用过程中出现了重大问题才会修改。至少要在一个浏览器中实现,提供 polyfill 或者 babel 插件。
stage-4 – Finished: will be added to the next yearly release. 已经准备就绪,该特性会出现在下个版本的 ECMAScript 规范之中。
当前的 stage 1- 3 列表可以查看这里 –> ECMAScript proposals
已经正式发布的特性索引
Proposal
Author
Champion(s)
TC39 meeting notes
Expected Publication Year
Array.prototype.includes
Domenic Denicola
Domenic DenicolaRick Waldron
November 2015
2016
Exponentiation operator
Rick Waldron
Rick Waldron
January 2016
2016
Object.values/Object.entries
Jordan Harband
Jordan Harband
March 2016
2017
String padding
Jordan Harband
Jordan HarbandRick Waldron
May 2016
2017
Object.getOwnPropertyDescriptors
Jordan HarbandAndrea Giammarchi
Jordan HarbandAndrea Giammarchi
May 2016
2017
Trailing commas in function parameter lists and calls
Jeff Morrison
Jeff Morrison
July 2016
2017
Async functions
Brian Terlson
Brian Terlson
July 2016
2017
Shared memory and atomics
Lars T Hansen
Lars T Hansen
January 2017
2017
Lifting template literal restriction
Tim Disney
Tim Disney
March 2017
2018
s (dotAll) flag for regular expressions
Mathias Bynens
Brian TerlsonMathias Bynens
November 2017
2018
RegExp named capture groups
Gorkem YakinDaniel Ehrenberg
Daniel EhrenbergBrian TerlsonMathias Bynens
November 2017
2018
Rest/Spread Properties
Sebastian Markbåge
Sebastian Markbåge
January 2018
2018
RegExp Lookbehind Assertions
Gorkem YakinNozomu KatōDaniel Ehrenberg
Daniel EhrenbergMathias Bynens
January 2018
2018
RegExp Unicode Property Escapes
Mathias Bynens
Brian TerlsonDaniel EhrenbergMathias Bynens
January 2018
2018
Promise.prototype.finally
Jordan Harband
Jordan Harband
January 2018
2018
Asynchronous Iteration
Domenic Denicola
Domenic Denicola
January 2018
2018
Optional catch binding
Michael Ficarra
Michael Ficarra
May 2018
2019
JSON superset
Richard Gibson
Mark MillerMathias Bynens
May 2018
2019
ES7 新特性(ECMAScript 2016)
ES7 在 ES6 的基础上主要添加了两项内容:
Array.prototype.includes()方法
求幂运算符(**)
Array.prototype.includes()方法
includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false。
var array = [1, 2, 3];
console.log(array.includes(2));
// expected output: true
var pets = [‘cat’, ‘dog’, ‘bat’];
console.log(pets.includes(‘cat’));
// expected output: true
console.log(pets.includes(‘at’));
// expected output: false
Array.prototype.includes()方法接收两个参数:
要搜索的值
搜索的开始索引。
当第二个参数被传入时,该方法会从索引处开始往后搜索(默认索引值为 0)。若搜索值在数组中存在则返回 true,否则返回 false。且看下面示例:
[‘a’, ‘b’, ‘c’, ‘d’].includes(‘b’) // true
[‘a’, ‘b’, ‘c’, ‘d’].includes(‘b’, 1) // true
[‘a’, ‘b’, ‘c’, ‘d’].includes(‘b’, 2) // false
乍一看,includes 的作用跟数组的 indexOf 重叠,为什么要特意增加这么一个 api 呢?主要区别有以下几点:
返回值。看一个函数,先看他们的返回值。indexOf 的返回数是值型的,includes 的返回值是布尔型,所以在 if 条件判断的时候 includes 要简单得多,而 indexOf 需要多写一个条件进行判断。
var ary = [1];
if (ary.indexOf(1) !== -1) {
console.log(“ 数组存在 1 ”)
}
if (ary.includes(1)) {
console.log(“ 数组存在 1 ”)
}
NaN 的判断。如果数组中有 NaN,你又正好需要判断数组是否有存在 NaN,这时你使用 indexOf 是无法判断的,你必须使用 includes 这个方法。
var ary1 = [NaN];
console.log(ary1.indexOf(NaN))//-1
console.log(ary1.includes(NaN))//true
当数组的有空的值的时候,includes 会认为空的值是 undefined,而 indexOf 不会。
var ary1 = new Array(3);
console.log(ary1.indexOf(undefined));//-1
console.log(ary1.includes(undefined))//true
求幂运算符(**)
加 / 减法我们通常都是用其中缀形式,直观易懂。在 ECMAScript2016 中,我们可以使用 ** 来替代 Math.pow。
4 ** 3 // 64
效果等同于
Math.pow(4,3)
值得一提的是,作为中缀运算符,** 还支持以下操作
let n = 4;
n **= 3;
// 64
ES8 新特性(ECMAScript 2017)
在 2017 年 1 月的 TC39 会议上,ECMAScript 2017 的最后一个功能“Shared memory and atomics”推进到第 4 阶段。这意味着它的功能集现已完成。
ECMAScript 2017 特性一览
主要新功能:
异步函数 Async Functions(Brian Terlson)
共享内存和 Atomics(Lars T. Hansen)
次要新功能:
Object.values / Object.entries(Jordan Harband)
String padding(Jordan Harband,Rick Waldron)
Object.getOwnPropertyDescriptors()(Jordan Harband,Andrea Giammarchi)
函数参数列表和调用中的尾逗号(Jeff Morrison)
Async Functions
Async Functions 也就是我们常说的 Async/Await,相信大家对于这个概念都已经不陌生了。Async/Await 是一种用于处理 JS 异步操作的语法糖,可以帮助我们摆脱回调地狱,编写更加优雅的代码。
通俗的理解,async 关键字的作用是告诉编译器对于标定的函数要区别对待。当编译器遇到标定的函数中的 await 关键字时,要暂时停止运行,带到 await 标定的函数处理完毕后,再进行相应操作。如果该函数 fulfiled 了,则返回值是 fulfillment value,否则得到的就是 reject value。
下面通过拿普通的 promise 写法来对比,就很好理解了:
async function asyncFunc() {
const result = await otherAsyncFunc();
console.log(result);
}
// Equivalent to:
function asyncFunc() {
return otherAsyncFunc()
.then(result => {
console.log(result);
});
}
按顺序处理多个异步函数的时候优势更为明显:
async function asyncFunc() {
const result1 = await otherAsyncFunc1();
console.log(result1);
const result2 = await otherAsyncFunc2();
console.log(result2);
}
// Equivalent to:
function asyncFunc() {
return otherAsyncFunc1()
.then(result1 => {
console.log(result1);
return otherAsyncFunc2();
})
.then(result2 => {
console.log(result2);
});
}
并行处理多个异步函数:
async function asyncFunc() {
const [result1, result2] = await Promise.all([
otherAsyncFunc1(),
otherAsyncFunc2(),
]);
console.log(result1, result2);
}
// Equivalent to:
function asyncFunc() {
return Promise.all([
otherAsyncFunc1(),
otherAsyncFunc2(),
])
.then([result1, result2] => {
console.log(result1, result2);
});
}
处理错误:
async function asyncFunc() {
try {
await otherAsyncFunc();
} catch (err) {
console.error(err);
}
}
// Equivalent to:
function asyncFunc() {
return otherAsyncFunc()
.catch(err => {
console.error(err);
});
}
Async Functions 若是要展开去讲,可以占用很大段的篇幅。鉴于本文是一篇介绍性文章,再次不再进行深入。
SharedArrayBuffer 和 Atomics
注,如果之前您没有接触过 ArrayBuffer 相关知识的话,建议您从内存管理速成教程系列漫画解说入门,强推:A crash course in memory management[A cartoon intro to ArrayBuffers and SharedArrayBuffers](https://hacks.mozilla.org/201…[Avoiding race conditions in SharedArrayBuffers with Atomics](https://hacks.mozilla.org/201…
ECMAScript 2017 特性 SharedArrayBuffer 和 atomics”,由 Lars T. Hansen 设计。它引入了一个新的构造函数 SharedArrayBuffer 和 具有辅助函数的命名空间对象 Atomics。
在我们开始之前,让我们澄清两个相似但截然不同的术语:并行(Parallelism) 和 并发(Concurrency)。他们存在许多定义,我使用的定义如下
并行(Parallelism) (parallel 并行 vs. serial 串行):同时执行多个任务;
并发(Concurrency) (concurrent 并发 vs. sequential 连续):在重叠的时间段内(而不是一个接一个)执行几个任务。
JS 并行的历史
JavaScript 在单线程中执行。某些任务可以异步执行:浏览器通常会在单线程中运行这些任务,然后通过回调将结果重新加入到单线程中。
Web workers 将任务并行引入了 JavaScript:这些是相对重量级的进程。每个 workers 都有自己的全局环境。默认情况下,不共享任何内容。workers 之间的通信(或在 workers 和主线程之间的通信)发展:
起初,你只能发送和接收字符串。
然后,引入结构化克隆:可以发送和接收数据副本。结构化克隆适用于大多数数据(JSON 数据,TypedArray,正则表达式,Blob 对象,ImageData 对象等)。它甚至可以正确处理对象之间的循环引用。但是,不能克隆 error 对象,function 对象和 DOM 节点。
可在 workers 之间的转移数据: 当接收方获得数据时,发送方失去访问权限。
通过 WebGL 使用 GPU 计算(它倾向于数据并行处理)
共享数组缓冲区(Shared Array Buffers)
共享阵列缓冲区是更高并发抽象的基本构建块。它们允许您在多个 workers 和主线程之间共享 SharedArrayBuffer 对象的字节 (该缓冲区是共享的,用于访问字节,将其封装在一个 TypedArray 中) 这种共享有两个好处:
你可以更快地在 workers 之间共享数据。workers 之间的协调变得更简单和更快(与 postMessage() 相比)。
// main.js
const worker = new Worker(‘worker.js’);
// 要分享的 buffer
const sharedBuffer = new SharedArrayBuffer(// (A)
10 * Int32Array.BYTES_PER_ELEMENT); // 10 elements
// 使用 Worker 共用 sharedBuffer
worker.postMessage({sharedBuffer}); // clone
// 仅限本地使用
const sharedArray = new Int32Array(sharedBuffer); // (B)
创建一个共享数组缓冲区(Shared Array Buffers)的方法与创建普通的数组缓冲区 (Array Buffer) 类似:通过调用构造函数,并以字节的形式指定缓冲区的大小(行 A)。你与 workers 共享的是 缓冲区(buffer)。对于你自己的本地使用,你通常将共享数组缓冲区封装在 TypedArray 中(行 B)。
workers 的实现如下所列。
// worker.js
self.addEventListener(‘message’, function (event) {
const {sharedBuffer} = event.data;
const sharedArray = new Int32Array(sharedBuffer); // (A)
// ···
});
sharedArrayBuffer 的 API
构造函数:
new SharedArrayBuffer(length)
创建一个 length 字节的 buffer(缓冲区)。
静态属性:
get SharedArrayBuffer[Symbol.species]
默认情况下返回 this。覆盖以控制 slice() 的返回。
实例属性:
get SharedArrayBuffer.prototype.byteLength()
返回 buffer(缓冲区) 的字节长度。
SharedArrayBuffer.prototype.slice(start, end)
创建一个新的 this.constructor[Symbol.species] 实例,并用字节填充从(包括)开始到(不包括)结束的索引。
Atomics: 安全访问共享数据
举一个例子
// main.js
sharedArray[1] = 11;
sharedArray[2] = 22;
在单线程中,您可以重新排列这些写入操作,因为在中间没有读到任何内容。对于多线程,当你期望以特定顺序执行写入操作时,就会遇到麻烦:
// worker.js
while (sharedArray[2] !== 22) ;
console.log(sharedArray[1]); // 0 or 11
Atomics 方法可以用来与其他 workers 进行同步。例如,以下两个操作可以让你读取和写入数据,并且不会被编译器重新排列:
Atomics.load(ta : TypedArray, index)
Atomics.store(ta : TypedArray, index, value : T)
这个想法是使用常规操作读取和写入大多数数据,而 Atomics 操作(load,store 和其他操作)可确保读取和写入安全。通常,您将使用自定义同步机制,例如锁,其实现基于 Atomics。
这是一个非常简单的例子,它总是有效的:
// main.js
console.log(‘notifying…’);
Atomics.store(sharedArray, 0, 123);
// worker.js
while (Atomics.load(sharedArray, 0) !== 123) ;
console.log(‘notified’);
Atomics 的 API
Atomic 函数的主要操作数必须是 Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array 或 Uint32Array 的一个实例。它必须包裹一个 SharedArrayBuffer。
所有函数都以 atomically 方式进行操作。存储操作的顺序是固定的并且不能由编译器或 CPU 重新排序。
加载和存储
Atomics.load(ta : TypedArray<T>, index) : T
读取和返回 ta[index] 上的元素,返回数组指定位置上的值。
Atomics.store(ta : TypedArray<T>, index, value : T) : T
在 ta[index] 上写入 value,并且返回 value。
Atomics.exchange(ta : TypedArray<T>, index, value : T) : T
将 ta[index] 上的元素设置为 value,并且返回索引 index 原先的值。
Atomics.compareExchange(ta : TypedArray<T>, index, expectedValue, replacementValue) : T
如果 ta[index] 上的当前元素为 expectedValue , 那么使用 replacementValue 替换。并且返回索引 index 原先(或者未改变)的值。
简单修改 TypeArray 元素
以下每个函数都会在给定索引处更改 TypeArray 元素:它将一个操作符应用于元素和参数,并将结果写回元素。它返回元素的原始值。
Atomics.add(ta : TypedArray<T>, index, value) : T
执行 ta[index] += value 并返回 ta[index] 的原始值。
Atomics.sub(ta : TypedArray<T>, index, value) : T
执行 ta[index] -= value 并返回 ta[index] 的原始值。
Atomics.and(ta : TypedArray<T>, index, value) : T
执行 ta[index] &= value 并返回 ta[index] 的原始值。
Atomics.or(ta : TypedArray<T>, index, value) : T
执行 ta[index] |= value 并返回 ta[index] 的原始值。
Atomics.xor(ta : TypedArray<T>, index, value) : T
执行 ta[index] ^= value 并返回 ta[index] 的原始值。
等待和唤醒
Atomics.wait(ta: Int32Array, index, value, timeout=Number.POSITIVE_INFINITY) : (‘not-equal’ | ‘ok’ | ‘timed-out’)
如果 ta[index] 的当前值不是 value,则返回 ‘not-equal’。否则继续等待,直到我们通过 Atomics.wake() 唤醒或直到等待超时。在前一种情况下,返回 ‘ok’。在后一种情况下,返回 ’timed-out’。timeout 以毫秒为单位。记住此函数执行的操作:“如果 ta[index] 为 value,那么继续等待”。
Atomics.wake(ta : Int32Array, index, count)
唤醒等待在 ta[index] 上的 count workers。
Object.values and Object.entries
Object.values() 方法返回一个给定对象自己的所有可枚举属性值的数组,值的顺序与使用 for…in 循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性)。
obj 参数是需要待操作的对象。可以是一个对象,或者一个数组(是一个带有数字下标的对象,[10,20,30] -> {0: 10,1: 20,2: 30})。
const obj = {x: ‘xxx’, y: 1};
Object.values(obj); // [‘xxx’, 1]
const obj = [‘e’, ‘s’, ‘8’]; // 相当于 {0: ‘e’, 1: ‘s’, 2: ‘8’};
Object.values(obj); // [‘e’, ‘s’, ‘8’]
// 当我们使用数字键值时,返回的是数字排序
// 根据键值排序
const obj = {10: ‘xxx’, 1: ‘yyy’, 3: ‘zzz’};
Object.values(obj); // [‘yyy’, ‘zzz’, ‘xxx’]
Object.values(‘es8’); // [‘e’, ‘s’, ‘8’]
Object.entries 方法返回一个给定对象自身可遍历属性 [key, value] 的数组,排序规则和 Object.values 一样。这个方法的声明比较琐碎:
const obj = {x: ‘xxx’, y: 1};
Object.entries(obj); // [[‘x’, ‘xxx’], [‘y’, 1]]
const obj = [‘e’, ‘s’, ‘8’];
Object.entries(obj); // [[‘0’, ‘e’], [‘1’, ‘s’], [‘2’, ‘8’]]
const obj = {10: ‘xxx’, 1: ‘yyy’, 3: ‘zzz’};
Object.entries(obj); // [[‘1’, ‘yyy’], [‘3’, ‘zzz’], [’10’: ‘xxx’]]
Object.entries(‘es8’); // [[‘0’, ‘e’], [‘1’, ‘s’], [‘2’, ‘8’]]
String padding
为 String 对象增加了 2 个函数:padStart 和 padEnd。
像它们名字那样,这几个函数的主要目的就是填补字符串的首部和尾部,为了使得到的结果字符串的长度能达到给定的长度。你可以通过特定的字符,或者字符串,或者默认的空格填充它。下面是函数的声明:
str.padStart(targetLength [, padString])
str.padEnd(targetLength [, padString])
这些函数的第一个参数是 targetLength(目标长度),这个是结果字符串的长度。第二个参数是可选的 padString(填充字符),一个用于填充到源字符串的字符串。默认值是空格。
‘es8’.padStart(2); // ‘es8’
‘es8’.padStart(5); // ‘ es8’
‘es8’.padStart(6, ‘woof’); // ‘wooes8’
‘es8’.padStart(14, ‘wow’); // ‘wowwowwowwoes8’
‘es8’.padStart(7, ‘0’); // ‘0000es8’
‘es8’.padEnd(2); // ‘es8’
‘es8’.padEnd(5); // ‘es8 ‘
‘es8’.padEnd(6, ‘woof’); // ‘es8woo’
‘es8’.padEnd(14, ‘wow’); // ‘es8wowwowwowwo’
‘es8’.padEnd(7, ‘6’); // ‘es86666’
Object.getOwnPropertyDescriptors
getOwnPropertyDescriptors 方法返回指定对象所有自身属性的描述对象。属性描述对象是直接在对象上定义的,而不是继承于对象的原型。ES2017 加入这个函数的主要动机在于方便将一个对象深度拷贝给另一个对象,同时可以将 getter/setter 拷贝。声明如下:
Object.getOwnPropertyDescriptors(obj)
obj 是待操作对象。返回的描述对象键值有:configurable, enumerable, writable, get, set and value。
const obj = {
get es7() { return 777;},
get es8() { return 888;}
};
Object.getOwnPropertyDescriptor(obj);
// {
// es7: {
// configurable: true,
// enumerable: true,
// get: function es7(){}, //the getter function
// set: undefined
// },
// es8: {
// configurable: true,
// enumerable: true,
// get: function es8(){}, //the getter function
// set: undefined
// }
// }
结尾逗号
结尾逗号用代码展示非常明了:
// 参数定义时
function foo(
param1,
param2,
) {}
// 函数调用时
foo(
‘abc’,
‘def’,
);
// 对象中
let obj = {
first: ‘Jane’,
last: ‘Doe’,
};
// 数组中
let arr = [
‘red’,
‘green’,
‘blue’,
];
这个改动有什么好处呢?
首先,重新排列项目更简单,因为如果最后一项更改其位置,则不必添加和删除逗号。
其次,它可以帮助版本控制系统跟踪实际发生的变化。例如,从:
[
‘foo’
]
修改为
[
‘foo’,
‘bar’
]
导致线条 ’foo’ 和线条 ’bar’ 被标记为已更改,即使唯一真正的变化是后一条线被添加。
ES9 新特性(ECMAScript 2018)
ES9 的新特性索引如下:
主要新功能:
异步迭代(Domenic Denicola,Kevin Smith)
Rest/Spread 属性(SebastianMarkbåge)
新的正则表达式功能:
RegExp named capture groups(Gorkem Yakin,Daniel Ehrenberg)
RegExp Unicode Property Escapes(Mathias Bynens)
RegExp Lookbehind Assertions(Gorkem Yakin,NozomuKatō,Daniel Ehrenberg)
s (dotAll) flag for regular expressions(Mathias Bynens)
其他新功能:
Promise.prototype.finally()(Jordan Harband)
模板字符串修改(Tim Disney)
异步迭代
首先来回顾一下同步迭代器:
ES6 引入了同步迭代器,其工作原理如下:
Iterable:一个对象,表示可以通过 Symbol.iterator 方法进行迭代。
Iterator:通过调用 iterable [Symbol.iterator] ()返回的对象。它将每个迭代元素包装在一个对象中,并通过其 next()方法一次返回一个。
IteratorResult:返回的对象 next()。属性 value 包含一个迭代的元素,属性 done 是 true 后最后一个元素。
示例:
const iterable = [‘a’, ‘b’];
const iterator = iterable[Symbol.iterator]();
iterator.next()
// {value: ‘a’, done: false}
iterator.next()
// {value: ‘b’, done: false}
iterator.next()
// {value: undefined, done: true}
异步迭代器
先前的迭代方式是同步的,并不适用于异步数据源。例如,在以下代码中,readLinesFromFile()无法通过同步迭代传递其异步数据:
for (const line of readLinesFromFile(fileName)) {
console.log(line);
}
异步迭代器和常规迭代器的工作方式非常相似,但是异步迭代器涉及 promise:
async function example() {
// 普通迭代器:
const iterator = createNumberIterator();
iterator.next(); // Object {value: 1, done: false}
iterator.next(); // Object {value: 2, done: false}
iterator.next(); // Object {value: 3, done: false}
iterator.next(); // Object {value: undefined, done: true}
// 异步迭代器:
const asyncIterator = createAsyncNumberIterator();
const p = asyncIterator.next(); // Promise
await p;// Object {value: 1, done: false}
await asyncIterator.next(); // Object {value: 2, done: false}
await asyncIterator.next(); // Object {value: 3, done: false}
await asyncIterator.next(); // Object {value: undefined, done: true}
}
异步迭代器对象的 next()方法返回了一个 Promise,解析后的值跟普通的迭代器类似。用法:iterator.next().then(({value, done})=> {//{value:‘some val’, done: false}}
const promises = [
new Promise(resolve => resolve(1)),
new Promise(resolve => resolve(2)),
new Promise(resolve => resolve(3)),
];
async function test() {
for await (const p of promises) {
console.log(p);
}
}
test(); //1 ,2 3
Rest/Spread 属性
这个就是我们通常所说的 rest 参数和扩展运算符,这项特性在 ES6 中已经引入,但是 ES6 中的作用对象仅限于数组:
restParam(1, 2, 3, 4, 5);
function restParam(p1, p2, …p3) {
// p1 = 1
// p2 = 2
// p3 = [3, 4, 5]
}
const values = [99, 100, -1, 48, 16];
console.log(Math.max(…values) ); // 100
在 ES9 中,为对象提供了像数组一样的 rest 参数和扩展运算符:
const obj = {
a: 1,
b: 2,
c: 3
}
const {a, …param} = obj;
console.log(a) //1
console.log(param) //{b: 2, c: 3}
function foo({a, …param}) {
console.log(a); //1
console.log(param) //{b: 2, c: 3}
}
正则表达式命名捕获组
编号的捕获组
// 正则表达式命名捕获组
const RE_DATE = /([0-9]{4})-([0-9]{2})-([0-9]{2})/;
const matchObj = RE_DATE.exec(‘1999-12-31’);
const year = matchObj[1]; // 1999
const month = matchObj[2]; // 12
const day = matchObj[3]; // 31
通过数字引用捕获组有几个缺点:
找到捕获组的数量是一件麻烦事:必须使用括号。
如果要了解组的用途,则需要查看正则表达式。
如果更改捕获组的顺序,则还必须更改匹配代码。
命名的捕获组
ES9 中可以通过名称来识别捕获组:(?<year>[0-9]{4})
在这里,我们用名称标记了前一个捕获组 year。该名称必须是合法的 JavaScript 标识符(认为变量名称或属性名称)。匹配后,您可以通过访问捕获的字符串 matchObj.groups.year 来访问。
让我们重写前面的代码:
const RE_DATE = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
const matchObj = RE_DATE.exec(‘1999-12-31’);
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31
// 使用解构语法更为简便
const {groups: {day, year}} = RE_DATE.exec(‘1999-12-31’);
console.log(year); // 1999
console.log(day); // 31
可以发现,命名捕获组有以下优点:
找到捕获组的“ID”更容易。
匹配代码变为自描述性的,因为捕获组的 ID 描述了正在捕获的内容。
如果更改捕获组的顺序,则无需更改匹配代码。
捕获组的名称也使正则表达式更容易理解,因为您可以直接看到每个组的用途。
正则表达式 Unicode 转义
该特性允许您使用 \p{}通过提及大括号内的 Unicode 字符属性来匹配字符, 在正则表达式中使用标记 u (unicode) 设置。
/^\p{White_Space}+$/u.test(‘\t \n\r’)
// true
/^\p{Script=Greek}+$/u.test(‘μετά’)
// true
新方法匹配中文字符
由于在 Unicode 里面,中文字符对应的 Unicode Script 是 Han,于是我们就可以用这个 reg 来匹配中文:
/\p{Script=Han}/u
这样我们就可以不用记忆繁琐又不好记的 /[\u4e00-\u9fa5]/ 了,况且这个表达式已经有些年头了,说实话,后来又新增的属性为 Han 的字符并不在这个范围内,因此这个有年头 reg 并不一定好使。
我随便从网上找了一个 Unicode8.0 添加的中文字符“????”, 我测了一下两种 reg 的兼容性:
oldReg=/[\u4e00-\u9fa5]/
newReg=/\p{Script=Han}/u
oldReg.test(‘abc’)
// false
newReg.test(‘abc’)
// false
oldReg.test(‘ 地平线 ’)
// true
newReg.test(‘ 地平线 ’)
// true
oldReg.test(‘????’)
// false
newReg.test(‘????’)
// true
http://www.unicode.org/charts…
可以参考一下这个 PDF,是 Unicode 的汉字全集,从 524 页 9FA6 至 526 页(最后一页)用旧匹配方式都无法生效。
一些对于 Unicode 的科普
Name:唯一名称,由大写字母,数字,连字符和空格组成。例如:
A:Name = LATIN CAPITAL LETTER A
????:Name = GRINNING FACE
General_Category:对字符进行分类。例如:
X:General_Category = Lowercase_Letter
$:General_Category = Currency_Symbol
White_Space:用于标记不可见的间距字符,例如空格,制表符和换行符。例如:
T:White_Space = True
π:White_Space = False
Age:引入字符的 Unicode 标准版本。例如:欧元符号€在 Unicode 标准的 2.1 版中添加。
€:Age = 2.1
Script:是一个或多个书写系统使用的字符集合。
有些脚本支持多种写入系统。例如,拉丁文脚本支持英语,法语,德语,拉丁语等书写系统。
某些语言可以用多个脚本支持的多个备用写入系统编写。例如,土耳其语在 20 世纪初转换为拉丁文字之前使用了阿拉伯文字。
例子:
α:Script = Greek
Д:Script = Cyrillic
正则表达式的 Unicode 属性转义
匹配其属性 prop 具有值的所有字符 value:\p{prop=value}
匹配所有没有属性 prop 值的字符 value:\P{prop=value}
匹配二进制属性 bin_prop 为 True 的所有字符:\p{bin_prop}
匹配二进制属性 bin_prop 为 False 的所有字符:\P{bin_prop}
匹配空格:
/^\p{White_Space}+$/u.test(‘\t \n\r’)
//true
匹配字母:
/^\p{Letter}+$/u.test(‘πüé’)
//true
匹配希腊字母:
/^\p{Script=Greek}+$/u.test(‘μετά’)
//true
匹配拉丁字母:
/^\p{Script=Latin}+$/u.test(‘Grüße’)
//true
正则表达式反向断言
先来看下正则表达式先行断言是什么:
如获取货币的符号
const noReLookahead = /\D(\d+)/,
reLookahead = /\D(?=\d+)/,
match1 = noReLookahead.exec(‘$123.45’),
match2 = reLookahead.exec(‘$123.45’);
console.log(match1[0]); // $123
console.log(match2[0]); // $
在 ES9 中可以允许反向断言:
const reLookahead = /(?<=\D)[\d\.]+/;
match = reLookahead.exec(‘$123.45’);
console.log(match[0]); // 123.45
使用?<= 进行反向断言,可以使用反向断言获取货币的价格,而忽略货币符号。
正则表达式 dotAll 模式
正则表达式中点. 匹配除回车外的任何单字符,标记 s 改变这种行为,允许行终止符的出现,例如:
/hello.world/.test(‘hello\nworld’); // false
/hello.world/s.test(‘hello\nworld’); // true
Promise.prototype.finally()
这个基本没什么好讲的,看名字就能看懂了。其用法如下:
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
finally 的回调总会被执行。
模板字符串修改
ES2018 移除对 ECMAScript 在带标签的模版字符串中转义序列的语法限制。之前,u 开始一个 unicode 转义,x 开始一个十六进制转义,后跟一个数字开始一个八进制转义。这使得创建特定的字符串变得不可能,例如 Windows 文件路径 C:uuuxxx111。
要取消转义序列的语法限制,可在模板字符串之前使用标记函数 String.raw:
`\u{54}`
// “T”
String.raw`\u{54}`
// “\u{54}”
尾声
ECMAScript 的演化不会停止,但是我们完全没必要害怕。除了 ES6 这个史无前例的版本带来了海量的信息和知识点以外,之后每年一发的版本都仅仅带有少量的增量更新,一年更新的东西花半个小时就能搞懂了,完全没必要畏惧。
Stay hungry. Stay foolish.