ES2018 裏包括哪些新特性?

咱們一路奮戰,不是爲了改變世界,而是爲了避免讓世界改變咱們。javascript

——《熔爐》java

JavaScript 標準 ECMAScript 的推動是很是活躍的了,自 ES2015 開始,標準制定委員會 TC39 決定每一年爲一個週期,推出新版本標準,這無疑爲 JavaScript 語言自己注入了強大的活力。git

一個新特性在寫入標準前,通常須要通過 5 個階段:es6

  • Stage 0 - Strawman(展現階段):最初提交的想法。
  • Stage 1 - Proposal(徵求意見階段):一個正式提案文檔,且至少有一位 TC39 的成員支持,其中包括 API 示例。
  • Stage 2 - Draft(草案階段):特性規範的初始版本,具備兩個實驗性實現。
  • Stage 3 - Candidate(候選人階段):對提案規範進行評審,並從第三方廠商收集反饋。
  • Stage 4 - Finished(定案階段):提案已經準備好包含在 ECMAScript 中,可是在瀏覽器和 Node.js 中可能須要一些時間實現。

一個提案只要能進入 Stage 2,就差很少確定會包括在之後的正式標準裏面。ECMAScript 當前的全部提案,能夠在 TC39 的官方網站 GitHub.com/tc39/ecma26… 查看。github

在正式講解 ES2018 前,先回顧一下 ES2016 和 ES2017 中引入的新特性。正則表達式

ES2016

  1. 數組的 includes 方法
  2. 指數運算符 **,好比 a ** bMath.pow(a, b) 的效果同樣

ES2017

  1. 異步函數
  2. Object.values,返回由對象屬性值組成的數組
  3. Object.entries,返回由一個個由 [key, vlaue] 組成的數組
  4. Object.getOwnPropertyDescriptors,返回一個對象,包含目標對象身上全部的屬性描述符(.value.writable.get.set.configurable.enumerable
  5. 字符串的 padStartpadEnd 方法
  6. 定義對象、聲明數組以及在函數的參數列表中,可以使用尾逗號。
  7. 用來讀取和寫入到共享內存的 SharedArrayBufferAtomics

好了,如今來看下 ES2018 增長的新特性吧 😍。json

ES2018

異步迭代

若是一個異步函數中包含一個循環,循環裏的每一次迭代是發起一個異步請求。那麼怎麼保證在本次迭代的請求結束後,再進入下一次迭代呢?🤔數組

你可能想過像下面這樣作:瀏覽器

function process(array) {
    array.forEach(async i => {
        console.log(`#${i} request start`)
        const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${i}`)
        const json = await res.json()
        console.log(`#${i} request end`, json)
    })
}
複製代碼

執行後,發現結果並不對:異步

緣由是,數組的 forEach 方法每遍歷一次,都是以回調的方式處理當前成員的,每一個迭代之間沒有關聯,都是各自執行的。所以,請求是按照遍歷順序發出的,然而各個請求有不一樣的響應時間,打印順序跟請求順序多是不同的。

這與咱們的預期效果——保證在本次迭代的請求結束後,再進入下一次迭代並不符合。

那麼換個方式:

async function process(array) {
    for (let i of array) {
    	console.log(`#${i} request start`)
        const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${i}`)
        const json = await res.json()
        console.log(`#${i} request end`, json)
    }
}
複製代碼

Wow, 這樣是能夠的~

緣由是,在異步函數中咱們可使用 await 關鍵字,像書寫同步代碼同樣,書寫異步代碼。也就是說,等待當前異步代碼返回後,再進行後續的邏輯操做。

ES2018 對迭代異步函數進行了加強,引入了異步迭代器。與普通迭代器不一樣的是,異步迭代器的 next 方法返回的是一個 Promise。語法是 for..await..of,咱們將上面的例子換個寫法:

async function process(array) {
    const reqs = array.map(async id => {
        console.log(`#${id} request start`)
        const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`)
        return res.json()
    })
    for await (let json of reqs) {
        console.log(json)
    }
}
複製代碼

這種寫法老是能保證輸出結果和遍歷順序是一致的。

在此特別感謝 @sea_ljf 同窗的指正!

Promise.finally()

.finally() 方法老是會被執行,不管 Promise 最終狀態是 resolve 了,仍是 reject 了。相似於 try...catch...finally 裏的 finally 塊的做用。

async function process(array) {
    fetch('https://jsonplaceholder.typicode.com/todos/1')
        .then(response => response.json())
        .then(json => console.log(json))
        .catch(error => {
            console.log(error)
        })
        .finally(() => {
            // 這裏的代碼老是會被執行
            // 不管 Promise 最終狀態是 resolve 了,仍是 reject 了
        })
}
複製代碼

剩餘/擴展屬性

ES2015 引入了三個點(...)運算符,這個運算符既能用來收集函數的剩餘參數,也能夠用來擴展數組。

首先來看做爲剩餘參數運算符的例子:

doSomething(1, 2, 3, 4, 5)

function doSomething(p1, p2, ...p3) {
    // p1 的值爲 1
    // p2 的值爲 2
    // p3 是一個數組,值爲 [3, 4, 5]
}
複製代碼

再來看用作擴展運算符的例子:

const values = [99, 100, -1, 48, 16];
console.log( Math.max(...values) ); // 100
複製代碼

咱們能夠看到,擴展運算符與剩餘參數運算符的用法是互爲反向的。

ES2015 引入的 ... 運算符,實際上僅適應在對數組的操做上,ES2018 對其進行了加強,將擴展和收集參數的能力擴大到了對象。使得 ... 運算符也能夠用來收集對象的「剩餘屬性」。

一個基礎的例子:

const obj = { a: 1, b: 2, c: 3 }
const { a, ...x } = obj
// a 等於 1
// x 的值爲 { b: 2, c: 3 }
複製代碼

這裏的 a 對應的是 obj 的屬性 ax 則是由 obj 中除 a 屬性之外的其餘屬性組成的對象。

咱們借用這個例子裏的 obj,再來看一個例子:

doSomething(obj)

function doSomething({ a, ...x }) {
    // a = 1
    // x = { b: 2, c: 3 }
}
複製代碼

doSomething 函數接收一個對象參數,調用後,將這個對象拆分紅變量 ax

咱們也可使用 ... 運算符的擴展功能,將一個對象「擴展」進另外一個對象。

const obj1 = { a: 1, b: 2, c: 3 };
const obj2 = { ...obj1, z: 26 };
// obj2 值變成了 { a: 1, b: 2, c: 3, z: 26 }
複製代碼

使用這個特性,咱們還能夠實現對象的淺克隆:obj2 = { ...obj1 }

正則表達式的命名捕獲組

在 ES2018 以前,咱們若是要匹配相似 '2018-04-30' 這樣的字符串格式,可能會這樣作。

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
複製代碼

咱們從最終的匹配結果 match 身上,使用索引值,找到對應捕獲組匹配的內容。但帶來的一個問題是,以後若是匹配格式發生改變,那麼 match 對應索引值下內容的含義就不同了。這樣咱們勢必會修改代碼,來提供正確的邏輯。

而 ES2018 容許咱們爲捕獲組命名,命名方式是在 ( 前面使用符號 ?<name> 標識。

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
複製代碼

全部的命名組的匹配結果,能夠在結果對象的 groups 屬性中得到。沒有命中的捕獲組,取值時獲得 undefined

除此以外,命名捕獲組還能夠在字符串的 replace 方法中使用:

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>');
複製代碼

這裏咱們將 '2019-05-18' 格式化爲 '05-18-2019'。在替換字符串中,使用 $<name> 的形式加入該命名捕獲組匹配到的內容。

正則表達式的後行斷言

JavaScript 正則表達式自然支持前行斷言。

所謂前言斷行,是指 x 只有在 y 前面時才匹配,書寫形式如 /x(?=y)/。好比,下面的例子裏,咱們只匹配數字以前的美圓符號:

const
  reLookahead = /\$(?=\d+)/,
  match       = reLookahead.exec('$123');

console.log( match[0] ); // $
複製代碼

對應地,所謂後行斷斷言,是指 x 只有在 y 後面時才匹配,書寫形式如 /(?<=y)x/。一樣上面的例子,咱們只匹配美圓符號以後的數字:

const
  reLookahead = /(?<=\$)\d+/,
  match       = reLookahead.exec('$123');

console.log( match[0] ); // 123
複製代碼

正則表達式的 s 修飾符:dotAll 模式

正則表達式的 . 匹配任意字符,但有兩個例外:

  1. 不能識別碼點大於 0xFFFF 的 Unicode 字符,這些字符每一個佔用 4 個字節。
  2. 終止符:包括換行符(\n)、回車符(\r)在內的字符。

針對第一條限制,咱們可使用 u 修飾符解決;針對第二條限制,咱們則可使用 s 修飾符。

// . 不匹配 \n,因此正則表達式返回 false
/hello.world/.test('hello\nworld') // false

// 這樣就能夠了
/hello.world/s.test('hello\nworld') // true
複製代碼

正則表達式的 Unicode 屬性類

ES2018 引入了一種新的類的寫法 \p{...}\P{...},容許正則表達式匹配符合 Unicode 某種屬性的全部字符。

const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true
複製代碼

上面代碼中,\p{Script=Greek} 指定匹配一個希臘文字母,因此匹配 π 成功。

\P{…}\p{…} 的反向匹配,即匹配不知足條件的字符。

注意,這兩種類只對 Unicode 有效,因此使用的時候必定要加上 u 修飾符。若是不加 u 修飾符,就會報錯。

模板字符串微調

在 JavaScipt 中,字符串中的 \ 表示一個轉義字符,它提供了 5 種表達字符的方式:

'\z' === 'z'  // true(z 無特殊含義,直接輸出)
'\172' === 'z' // true(字符的八進制表示)
'\x7A' === 'z' // true(字符的十六進制表示)
'\u007A' === 'z' // true(字符的十六進制表示)
'\u{7A}' === 'z' // true(字符的十六進制表示,支持任意 Unicode 字符碼點)
複製代碼

這就帶來了一個問題,若是字符串中包含 \unicode\xerxes 的話,就會報錯,由於會被認爲是無效的字符轉義。

爲了解決這個問題,ES2018 放鬆了對標籤模板裏面的字符串轉義的限制。若是遇到不合法的字符轉義,就返回 undefined,而不是報錯,而且能夠從 raw 屬性上面能夠獲得原始字符串。

function tag(strs) {
  // strs[0] 等於 undefined
  // strs.raw[0] 等於 "\\unicode and \\u{55}";
}
tag`\unicode and \u{55}`
複製代碼

參考連接

  1. What’s New in ES2018, by Craig Buckler
  2. ECMAScript 6 入門, by 阮一峯

貢獻指北

感謝你花費寶貴的時間閱讀這篇文章。

歡樂的時光老是短暫的,文章到此結束了 🎉。若是你以爲個人這篇讓你的生活美好了一點點,歡迎鼓(diǎn)勵(zàn)😀。若是能在文章下面留下你寶貴的評論或意見是再合適不過的了,由於研究證實,參與討論比單純閱讀更能讓人對知識印象深入😉。

(完)

相關文章
相關標籤/搜索