你可能已經知道的 ES 2018 和 2019

本文首發於個人博客javascript

標準這事兒吧……

ES 2019(ES 10)標準於年前正式發佈,藉此機會,咱們來看看都有哪些特性有幸轉正吧。順帶把 ES 2018 的內容也補一下。java

ECMAScript 標準的制定過程,自 2015 年大改,至今已是第 5 個年頭了,想必你們都內心有數了。與 Java 等語言不一樣,JS 並不是先制定標準再開始使用,偏偏相反,是你們先用着,以爲合適的,才收錄進標準。標準的存在更像是一個「年度優秀特性合集」。對絕大部分開發者來講,一項特性進沒進標準不重要,Babel 支不支持才重要。標準你隨便寫,不用 Babel 算我輸。git

那麼接下來,咱們就來看看 2018 和 2019 兩個年度的大合集都有些啥吧。github

ES2018(ES9)

1)異步迭代器(Asynchronous Iteration)

總有那麼些時候,咱們會想要同步執行一些異步的操做,好比下面這樣的:正則表達式

const actions = [
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.resolve(3)
]
複製代碼

利用 async / await 語法,咱們能夠很輕鬆的作到這點。數據庫

async function process (actions) {
  for (const action of actions) {
    await asyncFunc(action)
  }
}
複製代碼

上面的寫法,會按順序執行 asyncFunc,上一個結束以後纔會開始下一個,每次獲得的 action 都是一個異步操做自己(好比這裏是一個 Promise 對象)。數組

ES 2018 爲咱們提供了一種新的方式,在前面代碼的基礎之上,讓每次獲得的 action 直接是異步操做完成以後的結果(好比這裏是 Promise 被 resolve 以後的結果)。異步

async function process (actions) {
  for await (const action of actions) {
    asyncFunc(action)
  }
}
複製代碼

2)Rest/Spread Properties 開始適用於對象

這是一個從 ES 2015 開始就被普遍使用的特性,只不過 ES 2015 的標準只支持用於數組,從 ES 2018 開始也支持對象了。async

事實上 Map、Set、String 一樣支持 ...,但具體是哪一個版本引入的我還真沒數。(反正我已經用了好久了,無論了)函數

3)Promise.finally

正如它的名字,finally。這也是個用了很久終於進標準的特性。

在處理 Promise 的返回時,咱們常常會遇到這樣的狀況:不管結果狀態是 resolved 仍是 rejected,都執行同樣的邏輯。

早先遇到這種狀況,咱們不得不在 then()catch() 裏都寫一遍,如今能夠一次性寫在 finally() 裏。一個 finally() 就等價於一組回調函數相同的 then()catch()

雖然名字叫「最終」,但並不表明這是 Promise 執行的終點。finally() 後面還能夠繼續跟 then()catch(),無限跟。

4)移除對「在‘帶標籤的模版字面量’中使用非法轉義序列」的限制

從這裏開始的內容比較高階,通常用不到,趕時間的話你能夠跳過,直接去看 ES 2019。

這一節的標題有點繞,咱們拆開來說。首先是「帶標籤的模版字面量」。

ES 2015 引入了「模板字面量」的特性,相信你們都很熟悉了,長這樣:

const name = 'John'
const greetings = `Hi, ${name}` // 'Hi, John'
複製代碼

這個特性有一個生僻用法,它容許咱們自定義一個字符串模板函數,好比下面這樣:

function myTag(strings, ...params) {
  // strings: ['that ', ' is a ', '']

  const name = params[0]
  const age = params[1]
  const title = age > 99 ? 'centenarian' : 'youngster'

  return strings[0] + name + strings[1] + title
}

const person = 'Mike'
const age = 28
const output = myTag`that ${ person } is a ${ age }`
// that Mike is a youngster
複製代碼

這就是「帶標籤的模版字面量」。儘管我嚴重懷疑這個用法的實用性(或許是以爲這樣更加語義化?普通函數語義也不差啊?),但 ES 2018 仍是選擇了對這個特性進行完善。

ES 2016 爲這個特性加入了對轉義序列的支持,好比八進制(\ 開頭)、十六進制(\x 開頭)、Unicode 字符(\u 開頭),但前提必須是一個有效的轉義序列。若是是無效的序列,會報錯。

latex`\u00A9`   // 合法,表示「版權符號」
latex`\unicode` // 不合法,報錯
複製代碼

ES 2018 去掉了這個限制,主要是考慮到對一些領域特定語言的支持,好比 LaTeX。(學術界一種經常使用的標記型語言,相似 HTML,其語法會用到大量形如轉義序列的指令,如\section\frac\sum 等)

但去掉限制只是說不報錯了,模板中的無效轉義序列會被替換爲 undefined。好比下面這樣:

function myTag (template, ...params) {
  console.log({ template, params })
}

const foo = 'foo'
const bar = 'bar'
myTag`aaa${foo}\unicode${bar}bbb`
/* { template: ['aaa', undefined, 'bbb', raw: ['aaa', '\unicode', 'bbb]], params: ['foo', 'bar'] } */
複製代碼

上面的代碼裏,template 是模板部分被 ${foo} 等變量分割造成的數組;params 就是 ${foo} 等變量組成的數組。能夠看到,\unicode 因爲是無效的轉義序列,被替換爲 undefined,但在 template.raw 裏得以保留。

template.raw 是「帶標籤的模版字面量」中 template 參數特有的一個屬性,保存了未被替換的原始字符串。

這樣一來,既避免了報錯,又保留了開發者自行處理這些轉義序列的能力。

5)關於正則表達式的一些改進

5.1)s 標誌(dotAll 模式)

在正則表達式中,點號 . 表示匹配任一單個字符,但這不包含換行符(如:\n\r\f 等)。

如今能夠經過在尾部增長 s 標誌的方式,讓它匹配了。

/hello.world/.test('hello\nworld')  // false
/hello.world/s.test('hello\nworld') // true
複製代碼

5.2)擴展 Unicode 匹配範圍

一直以來,要編寫正則表達式來匹配各類 Unicode 字符並不容易,像 \w\W\d 等都只能匹配英文字符和數字,對於除此以外的字符就很難匹配了,例如非英語的文字。

幸運的是,Unicode 爲每一個符號添加了元數據屬性,並使用它來對各類符號進行分組和描述。例如,Unicode 數據庫給全部印地語字符(हिन्दी)設置了 Script 屬性,取值爲 Devanagari(梵文),還設置了一個 Script_Extensions 屬性,一樣取值爲 Devanagari。咱們能夠經過搜索 Script=Devanagari 來獲得全部印地文字符。

ES 2018 容許正則表達式經過 \p{...} 來擴展 Unicode 符號的匹配範圍。例如:

// 擴展匹配範圍,容許匹配希臘字符
const reGreekSymbol = /\p{Script=Greek}/u
reGreekSymbol.test('π') // true

// 擴展匹配範圍,容許匹配 Emoji
const reEmoji = /\p{Emoji}\p{Emoji_Modifier}/u
reEmoji.test('✌🏽') //true
複製代碼

咱們還能夠經過 \P{...}(注意,大寫 P)來去反,縮小匹配範圍。

5.3)正則表達式命名捕獲組

正則表達式支持經過括號在一個表達式中指定多個捕獲組,就像下面這樣:

const
  reDate = /([0-9]{4})-([0-9]{2})-([0-9]{2})/,
  match  = reDate.exec('2019-02-11'),
  year   = match[1], // 2019
  month  = match[2], // 02
  day    = match[3]; // 11
複製代碼

這樣的代碼雖然能夠跑通,但閱讀起來比較難懂,並且修改正則有可能會影響到匹配內容的索引。

ES 2018 容許在 ( 後當即使用符號 ?<name> 對捕獲組進行命名,匹配失敗的會返回 undefined,就像下面這樣:

const
  reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/,
  match  = reDate.exec('2019-02-11'),
  year   = match.groups.year,  // 2019
  month  = match.groups.month, // 02
  day    = match.groups.day   // 11
複製代碼

命名捕獲組也能夠用在 replace() 中,用 $<name> 進行引用(注意,雖然這裏的語法和模板字面量很像,但並非)。例如改變日期格式的順序:

const
  reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/,
  d      = '2019-02-11',
  usDate = d.replace(reDate, '$<month>-$<day>-$<year>'); // 02-11-2019
複製代碼

5.4)正則表達式的反向斷言(lookbehind)

正則表達式支持正向斷言(lookahead),例如:

// 正向確定查找
/x(?=y)/ // 匹配 x,但僅當 x 後面緊跟着 y 時
/Jack(?=Sprat)/.exec('JackSprat') // 'Jack'
/Jack(?=Sprat)/.exec('JackFrost') // null
/Jack(?=Sprat|Frost)/.exec('JackFrost') // 'Jack'

// 正向否認查找
/x(?!y)/ // 匹配 x,但僅當 x 後面不緊跟着 y 時
/Jack(?!Sprat)/.exec('JackSprat') // null
/Jack(?!Sprat)/.exec('Jack Sprat') // 'Jack'
複製代碼

ES 2018 引入了工做方式相同,可是方向相反的反向斷言(lookbehind),語法上的差異就在於 ? 變成了 ?<,例如:

// 反向確定斷言
/(?<x)y/ // 匹配 y,但僅當它緊跟在 x 後面時 /(?<=\D)\d+/.exec('$123.89')[0] // 123.89 // 反向否認斷言 /(?<!x)y/ // 匹配 y,但僅當它緊跟在 x 後面時 /(?<!\D)\d+/.exec('$123.89')[0] // null 複製代碼

ES 2019(ES 10)

1)JSON 成爲 ECMAScript 的徹底子集

從學習 JSON 的第一課起,咱們就被告知 JSON 應該是專爲 JavaScript 而存在的,所以 JSON 是 JavaScript 的子集這一點應該毫無爭議啊,這算什麼新特性!?

然而細心的開發者卻發現,有兩個符號是例外:行分隔符(U + 2028)和段分隔符(U + 2029)。在 JSON.parse() 中使用這兩個會報語法錯誤。

ES 2019 把這兩個也收入囊中,從今日後,JSON 真正成爲 ECMAScript 的徹底子集,一個都很多。

2)更友好的 JSON.stringify()

過去,對於一些超出 Unicode 範圍的轉義序列,JSON.stringify() 會輸出未知字符。

JSON.stringify('\uDF06\uD834'); // '"��"'
JSON.stringify('\uDEAD'); // '"�"'
複製代碼

如今,JSON.stringify() 會爲其從新轉義,顯示爲有效的 Unicode 序列。

JSON.stringify('\uDF06\uD834'); // '"\\udf06\\ud834"'
JSON.stringify('\uDEAD'); // '"\\udead"'
複製代碼

這和 ES 2018 中對「帶標籤的模板字面量」的修正,彷佛有些許聯繫。結合歷代 ECMAScript 標準,ECMAScript 在處理 Unicode 的問題上着實下了很多功夫。

3)Function.prototpye.toString() 顯示更加完善

對一個函數使用 toString() 會返回函數定義的內容。

過去,返回的內容中 function 關鍵字和函數名之間的註釋,以及函數名和參數列表左括號之間的空格,是不會被打出來的。ES 2019 如今回精確返回這些內容,函數怎麼定義的,這就就怎麼顯示。

4)Array.prorptype.flat()Array.prorptype.flatMap()

ES 2019 爲數組新增兩個函數。

flat() 用於對數組進行降維,它能夠接收一個參數,用於指定降多少維,默認爲 1。降維最多降到一維。

const array = [1, [2, [3]]]
array.flat() // [1, 2, [3]]
array.flat(1) // [1, 2, [3]],默認降 1 維
array.flat(2) // [1, 2, 3]
array.flat(3) // [1, 2, 3],最多降到一維
複製代碼

flatMap() 容許在對數組進行降維以前,先進行一輪映射,用法和 map() 同樣。而後再將映射的結果下降一個維度。能夠說 arr.flatMap(fn) 等效於 arr.map(fn).flat(1)。(可是根據 MDNflatMap() 在效率上略勝一籌)

flatMap() 也能夠等效爲 reduce()concat() 的組合,下面這個案例來自 MDN,可是……這不是一個 map 就能搞定的事麼?

var arr1 = [1, 2, 3, 4];

arr1.flatMap(x => [x * 2]);
// 等價於
arr1.reduce((acc, x) => acc.concat([x * 2]), []);
// [2, 4, 6, 8]
複製代碼

flat()flatMap() 都是返回新的數組,原數組不變。

5)String.prototype.trimStart()String.prototype.trimEnd()

ES 2019 爲字符串也新增了兩個函數:trimStart()trimEnd()。用過 trim() 的朋友都知道了,這兩個函數各自負責只去掉單邊的多餘空格。trim() 是兩邊都去。

6)Object.fromEntries()

從名字就能看出來,這是 Object.entries() 的逆過程。

7)Symbol.prototype.description

Symbol 是 ES 2015 引入的新的原始類型,一般在建立 Symbol 時咱們會附加一段描述。過去,只有把這個 Symbol 轉成 String 才能看到這段描述,並且外層還套了個 'Symbol()' 字樣。ES 2019 爲 Symbol 新增了 description 屬性,專門用於查看這段描述。

const sym = Symbol('The description');
String(sym) // 'Symbol(The description)'
sym.description // 'The description'
複製代碼

8)可選的 catch 綁定

try...catch 的語法你們都很熟悉了,過去,catch 後面必須有一組括號,裏面用一個變量(一般叫 e 或者 err)表明錯誤信息對象。如今這部分是可選的了,若是異常處理部分不須要錯誤信息,咱們能夠把它省略,像寫 if...else 同樣寫 try...catch

try {
  throw new Error('Some Error')
} catch {
  handleError() // 這裏沒有用到錯誤信息,能夠省略 catch 後面的 (e)。
}
複製代碼

遺憾

ES 2019 收錄了很是多好用的特性,但仍是有不少咱們很是熟悉,甚至已經用了很久的特性沒能進入標準,好比:

  • Stage 3(明年見?)
    • Dynamic Import
    • 私有屬性
  • Stage 2(加油?)
    • 裝飾器
  • Stage 1(大家慢慢討論,咱們先用爲敬)
    • Observable
    • Promise.try
    • String.prototype.replaceAll
    • do

不過這不重要,標準只是官宣,只要 Babel 支持就好,哈哈哈哈哈哈。

相關文章
相關標籤/搜索