题记:本文提供了一个在线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 的批准。

  1. stage-0 - Strawman: just an idea, possible Babel plugin.
    任何讨论、想法、改变或者还没加到提案的特性都在这个阶段。只有TC39成员可以提交。

    当前的stage 0列表可以查看这里 –> Stage 0 Proposals

  2. stage-1 - Proposal: this is worth working on.

    什么是 Proposal?一份新特性的正式建议文档。提案必须指明此建议的潜在问题,例如与其他特性之间的关联,实现难点等。

  3. stage-2 - Draft: initial spec.

    什么是 Draft?草案是规范的第一个版本。其与最终标准中包含的特性不会有太大差别。

    草案之后,原则上只接受增量修改。这个阶段开始实验如何实现,实现形式包括polyfill, 实现引擎(提供草案执行本地支持),或者编译转换(例如babel)

  4. stage-3 - Candidate: complete spec and initial browser implementations.

    候选阶段,获得具体实现和用户的反馈。此后,只有在实现和使用过程中出现了重大问题才会修改。至少要在一个浏览器中实现,提供polyfill或者babel插件。

  5. 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 Denicola
Rick 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 Harband
Rick Waldron
May 2016 2017
Object.getOwnPropertyDescriptors Jordan Harband
Andrea Giammarchi
Jordan Harband
Andrea 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 Terlson
Mathias Bynens
November 2017 2018
RegExp named capture groups Gorkem Yakin
Daniel Ehrenberg
Daniel Ehrenberg
Brian Terlson
Mathias Bynens
November 2017 2018
Rest/Spread Properties Sebastian Markbåge Sebastian Markbåge January 2018 2018
RegExp Lookbehind Assertions Gorkem Yakin
Nozomu Katō
Daniel Ehrenberg
Daniel Ehrenberg
Mathias Bynens
January 2018 2018
RegExp Unicode Property Escapes Mathias Bynens Brian Terlson
Daniel Ehrenberg
Mathias 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 Miller
Mathias Bynens
May 2018 2019

ES7新特性(ECMAScript 2016)

ES7在ES6的基础上主要添加了两项内容:

  • Array.prototype.includes()方法
  • 求幂运算符(**)

Array.prototype.includes()方法

includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

123456789101112
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。 且看下面示例:

123
['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 需要多写一个条件进行判断。
1234567
var ary = [1];if (ary.indexOf(1) !== -1) {    console.log("数组存在1")}if (ary.includes(1)) {    console.log("数组存在1")}
  • NaN的判断。如果数组中有NaN,你又正好需要判断数组是否有存在NaN,这时你使用indexOf是无法判断的,你必须使用includes这个方法。
123
var ary1 = [NaN];console.log(ary1.indexOf(NaN))//-1console.log(ary1.includes(NaN))//true
  • 当数组的有空的值的时候,includes会认为空的值是undefined,而indexOf不会。
123
var ary1 = new Array(3);console.log(ary1.indexOf(undefined));//-1console.log(ary1.includes(undefined))//true

求幂运算符(**)

加/减法我们通常都是用其中缀形式,直观易懂。在ECMAScript2016中,我们可以使用**来替代Math.pow。

1
4 ** 3           // 64

效果等同于

1
Math.pow(4,3)

值得一提的是,作为中缀运算符,**还支持以下操作

123
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写法来对比,就很好理解了:

123456789101112
async function asyncFunc() {    const result = await otherAsyncFunc();    console.log(result);}

// Equivalent to:function asyncFunc() {    return otherAsyncFunc()    .then(result => {        console.log(result);    });}

按顺序处理多个异步函数的时候优势更为明显:

123456789101112131415161718
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);    });}

并行处理多个异步函数:

123456789101112131415161718
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);    });}

处理错误:

123456789101112131415
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
Avoiding race conditions in SharedArrayBuffers with Atomics


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() 相比)。

123456789101112
// main.jsconst worker = new Worker('worker.js');

// 要分享的bufferconst sharedBuffer = new SharedArrayBuffer( // (A)    10 * Int32Array.BYTES_PER_ELEMENT); // 10 elements

// 使用Worker共用sharedBufferworker.postMessage({sharedBuffer}); // clone

// 仅限本地使用const sharedArray = new Int32Array(sharedBuffer); // (B)

创建一个共享数组缓冲区(Shared Array Buffers)的方法与创建普通的数组缓冲区(Array Buffer)类似:通过调用构造函数,并以字节的形式指定缓冲区的大小(行A)。你与 workers 共享的是 缓冲区(buffer) 。对于你自己的本地使用,你通常将共享数组缓冲区封装在 TypedArray 中(行B)。

workers的实现如下所列。

123456
// worker.jsself.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: 安全访问共享数据

举一个例子

123
// main.jssharedArray[1] = 11;sharedArray[2] = 22;

在单线程中,您可以重新排列这些写入操作,因为在中间没有读到任何内容。 对于多线程,当你期望以特定顺序执行写入操作时,就会遇到麻烦:

123
// worker.jswhile (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。

这是一个非常简单的例子,它总是有效的:

1234567
// main.jsconsole.log('notifying...');Atomics.store(sharedArray, 0, 123);

// worker.jswhile (Atomics.load(sharedArray, 0) !== 123) ;console.log('notified');

Atomics 的 API

Atomic 函数的主要操作数必须是 Int8Array ,Uint8Array ,Int16Array ,Uint16Array ,Int32Array 或 Uint32Array 的一个实例。它必须包裹一个 SharedArrayBuffer 。

所有函数都以 atomically 方式进行操作。存储操作的顺序是固定的并且不能由编译器或 CPU 重新排序。

加载和存储

  • Atomics.load(ta : TypedArray, index) : T
    读取和返回 ta[index] 上的元素,返回数组指定位置上的值。
  • Atomics.store(ta : TypedArray, index, value : T) : T
    在 ta[index] 上写入 value,并且返回 value。
  • Atomics.exchange(ta : TypedArray, index, value : T) : T
    将 ta[index] 上的元素设置为 value ,并且返回索引 index 原先的值。
  • Atomics.compareExchange(ta : TypedArray, index, expectedValue, replacementValue) : T
    如果 ta[index] 上的当前元素为 expectedValue , 那么使用 replacementValue 替换。并且返回索引 index 原先(或者未改变)的值。

简单修改 TypeArray 元素

以下每个函数都会在给定索引处更改 TypeArray 元素:它将一个操作符应用于元素和参数,并将结果写回元素。它返回元素的原始值。

  • Atomics.add(ta : TypedArray, index, value) : T
    执行 ta[index] += value 并返回 ta[index] 的原始值。
  • Atomics.sub(ta : TypedArray, index, value) : T
    执行 ta[index] -= value 并返回 ta[index] 的原始值。
  • Atomics.and(ta : TypedArray, index, value) : T
    执行 ta[index] &= value 并返回 ta[index] 的原始值。
  • Atomics.or(ta : TypedArray, index, value) : T
    执行 ta[index] |= value 并返回 ta[index] 的原始值。
  • Atomics.xor(ta : TypedArray, 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})。

123456789101112
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 一样。这个方法的声明比较琐碎:

12345678910
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。

像它们名字那样,这几个函数的主要目的就是填补字符串的首部和尾部,为了使得到的结果字符串的长度能达到给定的长度。你可以通过特定的字符,或者字符串,或者默认的空格填充它。下面是函数的声明:

12
str.padStart(targetLength [, padString])str.padEnd(targetLength [, padString])

这些函数的第一个参数是 targetLength(目标长度),这个是结果字符串的长度。第二个参数是可选的 padString(填充字符),一个用于填充到源字符串的字符串。默认值是空格。

1234567891011
'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拷贝。声明如下:

1
Object.getOwnPropertyDescriptors(obj)

obj 是待操作对象。返回的描述对象键值有:configurable, enumerable, writable, get, set and value。

12345678910111213141516171819
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//   }// }

结尾逗号

结尾逗号用代码展示非常明了:

123456789101112131415161718192021222324
// 参数定义时function foo(    param1,    param2,) {}

// 函数调用时foo(    'abc',    'def',);

// 对象中let obj = {    first: 'Jane',    last: 'Doe',};

// 数组中let arr = [    'red',    'green',    'blue',];

这个改动有什么好处呢?

  • 首先,重新排列项目更简单,因为如果最后一项更改其位置,则不必添加和删除逗号。
  • 其次,它可以帮助版本控制系统跟踪实际发生的变化。例如,从:
123
[    'foo']

修改为

1234
[    '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 后最后一个元素。

示例:

12345678
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()无法通过同步迭代传递其异步数据:

123
for (const line of readLinesFromFile(fileName)) {    console.log(line);}

异步迭代器和常规迭代器的工作方式非常相似,但是异步迭代器涉及promise:

12345678910111213141516
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}}

123456789101112
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中的作用对象仅限于数组:

12345678910
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参数和扩展运算符:

12345678910111213
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}}

正则表达式命名捕获组

编号的捕获组

1234567
//正则表达式命名捕获组const RE_DATE = /([0-9]{4})-([0-9]{2})-([0-9]{2})/;

const matchObj = RE_DATE.exec('1999-12-31');const year = matchObj[1]; // 1999const month = matchObj[2]; // 12const day = matchObj[3]; // 31

通过数字引用捕获组有几个缺点:

  • 找到捕获组的数量是一件麻烦事:必须使用括号。
  • 如果要了解组的用途,则需要查看正则表达式。
  • 如果更改捕获组的顺序,则还必须更改匹配代码。

命名的捕获组

ES9中可以通过名称来识别捕获组:(?<year>[0-9]{4})

在这里,我们用名称标记了前一个捕获组year。该名称必须是合法的JavaScript标识符(认为变量名称或属性名称)。匹配后,您可以通过访问捕获的字符串matchObj.groups.year来访问。

让我们重写前面的代码:

1234567891011
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; // 1999const month = matchObj.groups.month; // 12const day = matchObj.groups.day; // 31

// 使用解构语法更为简便const {groups: {day, year}} = RE_DATE.exec('1999-12-31');console.log(year); // 1999console.log(day); // 31

可以发现,命名捕获组有以下优点:

  • 找到捕获组的“ID”更容易。
  • 匹配代码变为自描述性的,因为捕获组的ID描述了正在捕获的内容。
  • 如果更改捕获组的顺序,则无需更改匹配代码。
  • 捕获组的名称也使正则表达式更容易理解,因为您可以直接看到每个组的用途。

正则表达式 Unicode 转义

该特性允许您使用\p{}通过提及大括号内的Unicode字符属性来匹配字符,在正则表达式中使用标记 u(unicode) 设置。

1234
/^\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添加的中文字符“

细解JavaScript ES7 ES8 ES9 新特性的更多相关文章

  1. javaScript ES7 ES8 ES9 ES10新特性

    参考文献: https://tuobaye.com/2018/11/27/%E7%BB%86%E8%A7%A3JavaScript-ES7-ES8-ES9-%E6%96%B0%E7%89%B9%E6% ...

  2. es6/es7/es8常用新特性总结(超实用)

    本文标题有误导性,因为我其实想写node8的新特性,说实话一下子从node v1.x跳跃到node 8.x+ 真有点受宠若惊的感觉.一直觉得node 数组. 对象.序列等的处理没有python方便,因 ...

  3. atitit.jQuery Validate验证框架详解与ati Validate 设计新特性

    atitit.jQuery Validate验证框架详解与ati Validate 设计新特性 1. AtiValidate的目标1 2. 默的认校验规则1 2.1. 使用方式 1.metadata用 ...

  4. ECMAScript 2018(ES9)新特性简介

    目录 简介 异步遍历 Rest/Spread操作符和对象构建 Rest Spread 创建和拷贝对象 Spread和bject.assign() 的区别 正则表达式 promise.finally 模 ...

  5. ES9新特性

    这篇文章主要介绍ES2018(ES9)的新特性, 以及使用方法 JS是一门跨平台的语言, ES6也就是ECMAScript 2015 花费了5年的时间敲定, 是一次非常大的改版, 之后每年都有一个小版 ...

  6. ES6、ES7的一些新特性

    1.常见的就是let 和 const 命令 let 只在命令所在的代码块内有效 const声明一个只读的常量 2.变量的赋值 let [a, b, c] = [1, 2, 3]; 这样输出的话a=1, ...

  7. ElasticSearch7.3学习(三十)----ES7.X SQL新特性解析及使用Java api实现sql功能

    一.ES7 sql新特性 1.1 数据准备 创建索引及映射 建立价格.颜色.品牌.售卖日期 四个字段 PUT /tvs PUT /tvs/_mapping { "properties&quo ...

  8. DataNode 详解及HDFS 2.X新特性

    1. 工作机制 一个数据块在 DataNode 上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳. DataNode 启动后向 Name ...

  9. JavaScript:ES6的新特性

    1.关键字 const:修饰常量.ES6之前只有变量的声明字段var,ES6开始引入常量关键字,被修饰的变量无法被修改. <script type="text/javascript&q ...

随机推荐

  1. react一写工具

    动画库:React-transition-group ui框架:Ant Design

  2. template screen

    从全局搜索主机可看到此screen

  3. day 19 os模块的补充 序列化 json pickle

    os   模块 os.path.abspath  规范绝对路径 os.path.split() 把路径分成两段,第二段是一个文件或者是文件夹 os.path.dirname    取第一部分 os.p ...

  4. Python和Java的区别

    这里是我的一些总结,有些是参考别人的(在这里谢谢!!!) 区别: 1.Python比Java简单,学习成本低,开发效率高2.Java运行效率高于Python,尤其是纯Python开发的程序,效率极低3 ...

  5. linux中Nginx安装

    linux中Nginx安装 编译安装 ​ Nginx的优点太多,这里不再赘述,详情请看这篇博客深入理解nginx. ​ Nginx的安装有rpm包安装.编译安装和docker安装.本文将介绍编译安装方 ...

  6. 中标麒麟neokylin信息查看

    中标麒麟Neokylin系统版本信息: # nkvers ############## NeoKylin Linux Version################# Release: NeoKyli ...

  7. 一个有意义的Day类

    早晨去单位的路上听到电台里在说“Everyday is a new chance to change your life”,正好最近在学Python类的使用方法,于是我编了一个关于Day的类,以供参考 ...

  8. PHP按二维数组中的某个值重新排序数组 usort的使用方法

    $arr[0] = ['aa'=>123,'bb'=>'abc']; $arr[1] = ['aa'=>456,'bb'=>'dfe']; usort($arr,ss('aa' ...

  9. python-14-文件操作

    前言 python中对文件的读写也是非常方便的,本章节将讲解读.写.读写等常用操作.下面讲师必要的参数: 1.文件路径:必须得知道文件的路径,不然怎样进行读写? 2.编码方式:utf-8,gbk,gb ...

  10. 如何切换本地的GIT账号

    如何切换本地的GIT账号 1.为什么登陆第一次Git之后,就不用登陆了呢? 因为电脑已经将你的登陆凭据给保存起来了. 这也正是你不知道如何切换账号的原因. 2.在哪里能看已经保存的登陆凭证呢?并能够切 ...