本文首發於個人博客。javascript
ES 2019(ES 10)標準於年前正式發佈,藉此機會,咱們來看看都有哪些特性有幸轉正吧。順帶把 ES 2018 的內容也補一下。java
ECMAScript 標準的制定過程,自 2015 年大改,至今已是第 5 個年頭了,想必你們都內心有數了。與 Java 等語言不一樣,JS 並不是先制定標準再開始使用,偏偏相反,是你們先用着,以爲合適的,才收錄進標準。標準的存在更像是一個「年度優秀特性合集」。對絕大部分開發者來講,一項特性進沒進標準不重要,Babel 支不支持才重要。標準你隨便寫,不用 Babel 算我輸。git
那麼接下來,咱們就來看看 2018 和 2019 兩個年度的大合集都有些啥吧。github
總有那麼些時候,咱們會想要同步執行一些異步的操做,好比下面這樣的:正則表達式
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)
}
}
複製代碼
這是一個從 ES 2015 開始就被普遍使用的特性,只不過 ES 2015 的標準只支持用於數組,從 ES 2018 開始也支持對象了。async
事實上 Map、Set、String 一樣支持 ...
,但具體是哪一個版本引入的我還真沒數。(反正我已經用了好久了,無論了)函數
正如它的名字,finally。這也是個用了很久終於進標準的特性。
在處理 Promise 的返回時,咱們常常會遇到這樣的狀況:不管結果狀態是 resolved 仍是 rejected,都執行同樣的邏輯。
早先遇到這種狀況,咱們不得不在 then()
和 catch()
裏都寫一遍,如今能夠一次性寫在 finally()
裏。一個 finally()
就等價於一組回調函數相同的 then()
和 catch()
。
雖然名字叫「最終」,但並不表明這是 Promise 執行的終點。finally()
後面還能夠繼續跟 then()
和 catch()
,無限跟。
從這裏開始的內容比較高階,通常用不到,趕時間的話你能夠跳過,直接去看 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
參數特有的一個屬性,保存了未被替換的原始字符串。
這樣一來,既避免了報錯,又保留了開發者自行處理這些轉義序列的能力。
s
標誌(dotAll 模式)在正則表達式中,點號 .
表示匹配任一單個字符,但這不包含換行符(如:\n
、\r
、\f
等)。
如今能夠經過在尾部增長 s
標誌的方式,讓它匹配了。
/hello.world/.test('hello\nworld') // false
/hello.world/s.test('hello\nworld') // true
複製代碼
一直以來,要編寫正則表達式來匹配各類 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)來去反,縮小匹配範圍。
正則表達式支持經過括號在一個表達式中指定多個捕獲組,就像下面這樣:
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
複製代碼
正則表達式支持正向斷言(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 複製代碼
從學習 JSON 的第一課起,咱們就被告知 JSON 應該是專爲 JavaScript 而存在的,所以 JSON 是 JavaScript 的子集這一點應該毫無爭議啊,這算什麼新特性!?
然而細心的開發者卻發現,有兩個符號是例外:行分隔符(U + 2028)和段分隔符(U + 2029)。在 JSON.parse()
中使用這兩個會報語法錯誤。
ES 2019 把這兩個也收入囊中,從今日後,JSON 真正成爲 ECMAScript 的徹底子集,一個都很多。
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 的問題上着實下了很多功夫。
Function.prototpye.toString()
顯示更加完善對一個函數使用 toString()
會返回函數定義的內容。
過去,返回的內容中 function
關鍵字和函數名之間的註釋,以及函數名和參數列表左括號之間的空格,是不會被打出來的。ES 2019 如今回精確返回這些內容,函數怎麼定義的,這就就怎麼顯示。
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)
。(可是根據 MDN,flatMap()
在效率上略勝一籌)
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()
都是返回新的數組,原數組不變。
String.prototype.trimStart()
和 String.prototype.trimEnd()
ES 2019 爲字符串也新增了兩個函數:trimStart()
和 trimEnd()
。用過 trim()
的朋友都知道了,這兩個函數各自負責只去掉單邊的多餘空格。trim()
是兩邊都去。
Object.fromEntries()
從名字就能看出來,這是 Object.entries()
的逆過程。
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'
複製代碼
catch
綁定try...catch
的語法你們都很熟悉了,過去,catch
後面必須有一組括號,裏面用一個變量(一般叫 e
或者 err
)表明錯誤信息對象。如今這部分是可選的了,若是異常處理部分不須要錯誤信息,咱們能夠把它省略,像寫 if...else
同樣寫 try...catch
。
try {
throw new Error('Some Error')
} catch {
handleError() // 這裏沒有用到錯誤信息,能夠省略 catch 後面的 (e)。
}
複製代碼
ES 2019 收錄了很是多好用的特性,但仍是有不少咱們很是熟悉,甚至已經用了很久的特性沒能進入標準,好比:
不過這不重要,標準只是官宣,只要 Babel 支持就好,哈哈哈哈哈哈。