細解JavaScript ES7 ES8 ES9 新特性

導言:ECMAScript的演化不會中止,可是咱們徹底不必懼怕。除了ES6這個前所未有的版本帶來了海量的信息和知識點之外,以後每一年一發的版本都僅僅帶有少許的增量更新,一年更新的東西花半個小時就能搞懂了,徹底不必畏懼。本文將帶您花大約一個小時左右的時間,迅速過一遍ES7,ES8,ES9的新特性。javascript

想追求更好的閱讀體驗,請移步原文地址java

es_16_17_18

題記:本文提供了一個在線PPT版本,方便您瀏覽 細解JAVASCRIPT ES7 ES8 ES9 新特性 在線PPT vergit

本文的大部份內容譯自做者Axel Rauschmayer博士的網站,想了解更多關於做者的信息,能夠瀏覽Exploring JS: JavaScript books for programmersgithub

那些與ECMAScript有關的事情

誰在設計ECMAScript?

TC39 (Technical Committee 39)。正則表達式

TC39 是推動 JavaScript 發展的委員會。其會員都是公司(其中主要是瀏覽器廠商)。TC39 按期召開會議,會議由會員公司的表明與特邀專家出席。會議紀錄均可在網上查看,可讓你對 TC39 如何工做有一個清晰的概念。express

頗有意思的是,TC39 實行的是協商一致的原則:經過一項決議必須獲得每一位會員(公司表明)的同意。json

ECMAScript的發佈週期

在2015年發佈的 ECMAScript(ES6)新增內容不少,在 ES5 發佈近 6 年(2009-11 至 2015-6)以後纔將其標準化。兩個發佈版本之間時間跨度如此之大主要有兩大緣由:api

  • 比新版率先完成的特性,必須等待新版的完成才能發佈。
  • 那些須要花長時間完成的特性,也頂着很大的壓力被歸入這一版本,由於若是推遲到下一版本發佈意味着又要等好久,這種特性也會推遲新的發佈版本。

所以,從 ECMAScript 2016(ES7)開始,版本發佈變得更加頻繁,每一年發佈一個新版本,這麼一來新增內容也會更小。新版本將會包含每一年截止時間以前完成的全部特性。數組

ECMAScript的發佈流程

每一個 ECMAScript 特性的建議將會從階段 0 開始, 而後通過下列幾個成熟階段。其中從一個階段到下一個階段必須通過 TC39 的批准。promise

  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)

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)

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)

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

一些對於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.

相關文章
相關標籤/搜索