ES2018中4個(gè)有用的功能
ES2018 規(guī)范引入了四個(gè)新功能。這些功能包括異步迭代,rest/spread 屬性,Promise.prototype.finally() 和正則表達(dá)式改進(jìn)。本問(wèn)將幫你了解這些 ES2018 功能的工作原理及使用方法。
異步迭代
異步迭代是討論的比較少 ES2018 功能之一。雖然還有很多關(guān)于 ES2018 其他功能的討論,但幾乎沒(méi)有關(guān)于異步迭代這方面的內(nèi)容。通過(guò)異步迭代,我們可以得到異步的可迭代對(duì)象和迭代器。
這意味著你可以把 await 關(guān)鍵字與 for…of 循環(huán)放在一起使用。你可以用這些循環(huán)對(duì)可迭代對(duì)象進(jìn)行迭代。可迭代對(duì)象的包括數(shù)組、map、set,NodeList,函數(shù)的 arguments 參數(shù),TypedArray 等。
在 ES2018 之前,for...of 循環(huán)是同步的。如果你試著迭代涉及異步操作的可迭代對(duì)象并 await,則無(wú)法正常工作。循環(huán)本身會(huì)保持同步,基本上忽略 await ,并在其內(nèi)部的異步操作可以完成之前完成迭代。
- // 下面的代碼在 ES2018 之前不起作用,因?yàn)檠h(huán)保持同步。
- // 創(chuàng)建一個(gè)異步函數(shù):
- async function processResponses(someIterable) {
- // 對(duì)可迭代對(duì)象進(jìn)行迭代
- for (let item of someIterable) {
- // 通過(guò)異步操作處理項(xiàng)目,例如promise:
- await processItem(item)
- }
- }
同時(shí) for...of 循環(huán)也可以與異步代碼一起使用。也就是說(shuō)可以在遍歷可迭代對(duì)象時(shí)執(zhí)行一些異步操作。for...of 循環(huán)將會(huì)是異步的,讓你能夠等待異步操作完成。
需要記住的是在哪里使用 await 關(guān)鍵字。不需要把它放進(jìn)循環(huán)體中,應(yīng)該將其放在for...of關(guān)鍵字中 for 的后面?,F(xiàn)在當(dāng)你用 next() 方法獲取異步迭代器的下個(gè)值時(shí),將會(huì)得到一個(gè) Promise。如果你想了解更多信息,可以在 GitHub 上去看看(https://github.com/tc39/proposal-async-iteration)。
- // 創(chuàng)建一個(gè)異步函數(shù):
- async function processResponses(someIterable) {
- //遍歷可迭代對(duì)象并等待異步操作的結(jié)果
- for await (let item of someIterable) {
- processItem(item)
- }
- }
Rest/Spread 屬性
rest 和 spread 并不是真正的新功能。兩者都是在 ES6 中作為新的運(yùn)算符引入的,它們很快就開(kāi)始流行起來(lái)。可以說(shuō) JavaScript 程序員喜歡它們。唯一的問(wèn)題是它們只能用在數(shù)組和參數(shù)上,不過(guò) ES2018 把這兩個(gè)功能引入了對(duì)象中。
rest 和 spread 運(yùn)算符的語(yǔ)法都非常簡(jiǎn)單,由三個(gè)點(diǎn)(...)組成。這些點(diǎn)后面是要在其上使用 rest 或 spread 運(yùn)算符的對(duì)象。接下來(lái)簡(jiǎn)單的討論一下兩者的工作原理。
對(duì)象的 rest 運(yùn)算符
rest 運(yùn)算符使你可以將對(duì)象的所有剩余對(duì)象屬性屬性提取到新對(duì)象上。要注意這些屬性必須是可枚舉的。如果你已經(jīng)對(duì)某些屬性使用了分解,那么 rest 運(yùn)算符會(huì)只提取剩余的屬性。
- // Rest example:
- const daysObj = {
- one: 'Monday',
- two: 'Tuesday',
- three: 'Wednesday',
- four: 'Thursday',
- five: 'Friday'
- }
- //使用解構(gòu)將變量的前兩個(gè)屬性分配給變量。
- //然后,使用rest將其余屬性分配給第三個(gè)變量。
- const { one, two, ...restOfDays } = daysObj
- // rest 僅提取 "three", "four" 和 "five"
- // 因?yàn)槲覀円呀?jīng)提取了 "one" 和 "two"
- console.log(one)
- // Output:
- // 'Monday'
- console.log(two)
- // Output:
- // 'Tuesday'
- console.log(restOfDays)
- // Output:
- // { three: 'Wednesday', four: 'Thursday', five: 'Friday' }
如果要對(duì)對(duì)象使用 rest 運(yùn)算符,需要記住兩點(diǎn):首先,只能用一次,除非把它用在嵌套對(duì)象上。其次,必須在最后使用。這就是為什么在上面的例子中,在解構(gòu)前兩個(gè)屬性之后而不是之前看到它的原因。
- // 這行代碼不起作用,因?yàn)榘?nbsp;rest 運(yùn)算符用在了最前面:
- const { ...all, one, two } = { one: 1, two: 2, three: 3 }
- //這行能起作用:
- const { one, two, ...all } = { one: 1, two: 2, three: 3 }
- // 這行不起作用,因?yàn)橥患?jí)別上有多個(gè) rest 運(yùn)算符:
- const { one, ...some, ...end } = { /* some properties */ }
- // 這行能起作用,在多個(gè)級(jí)別上的多個(gè) rest 運(yùn)算符:
- const { one, {...secondLevel }, ...firstLevel } = { /* some properties */ }
對(duì)象的 spread 運(yùn)算符
spread 運(yùn)算符的作用是可以通過(guò)插入另一個(gè)對(duì)象的所有屬性來(lái)創(chuàng)建新對(duì)象。Spread 運(yùn)算符還允許你從多個(gè)對(duì)象插入屬性。也可以把這個(gè)運(yùn)算符與添加新屬性結(jié)合使用。
- // Spread example:
- const myOriginalObj = { name: 'Joe Doe', age: 33 }
- // 用 spread 運(yùn)算符創(chuàng)建新對(duì)象:
- const myNewObj = { ...myOriginalObj }
- console.log(myNewObj)
- // Output:
- // { name: 'Joe Doe', age: 33 }
- // 添加屬性的例子:
- const myOriginalObj = { name: 'Caesar' }
- // 用 spread 運(yùn)算符創(chuàng)建新對(duì)象
- // 并添加新的屬性“genre”:
- const myNewObj = { ...myOriginalObj, genre: 'Strategy' }
- console.log(myNewObj)
- // Output:
- // {
- // name: 'Caesar',
- // genre: 'Strategy'
- // }
- // Spread 運(yùn)算符并合并兩個(gè)對(duì)象:
- const myObjOne = { title: 'Eloquent JavaScript' }
- const myObjTwo = { author: 'Marijn Haverbeke' }
- const myNewObj = { ...myObjOne, ...myObjTwo }
- console.log(myNewObj)
- // Output:
- // {
- // title: 'Eloquent JavaScript',
- // author: 'Marijn Haverbeke'
- // }
當(dāng)從多個(gè)對(duì)象插入屬性并添加新屬性時(shí),順序很重要。
我來(lái)解釋一下,假設(shè)你要用 spread 運(yùn)算符基于兩個(gè)現(xiàn)有對(duì)象創(chuàng)建一個(gè)新對(duì)象。第一個(gè)已有對(duì)象中包含具有某些值的屬性 title。第二個(gè)對(duì)象也包含屬性 title,但是值不一樣。最終到底取哪個(gè) title?
答案是最后一個(gè)。如果對(duì)第一個(gè)對(duì)象使用 spread 運(yùn)算符,然后再對(duì)第二個(gè)對(duì)象使用,則第二個(gè) title 會(huì)生效。如果你將 spread 運(yùn)算符永在第二個(gè)對(duì)象上,則第一個(gè) title會(huì)生效。
- // Spread 運(yùn)算符并合并兩個(gè)對(duì)象:
- const myObjOne = {
- title: 'Eloquent JavaScript',
- author: 'Marijn Haverbeke',
- }
- const myObjTwo = {
- title: 'You Don\'t Know JS Yet',
- language: 'English'
- }
- // 用 spread 運(yùn)算符通過(guò)組合 “myObjOne” 和 “myObjTwo” 創(chuàng)建新對(duì)象
- // 注意:“myObjTwo” 中的 “title” 會(huì)將覆蓋 “myObjTwo” 的 “title”
- // 因?yàn)?ldquo; myObjTwo”排在最后。
- const myNewObj = { ...myObjOne, ...myObjTwo }
- console.log(myNewObj)
- // Output:
- // {
- // title: "You Don't Know JS Yet",
- // author: 'Marijn Haverbeke',
- // language: 'English'
- // }
- // 注意:“myObjOne” 中的 “title” 將覆蓋 “myObjTwo” 的 “title”
- const myNewObj = { ...myObjTwo, ...myObjOne }
- console.log(myNewObj)
- // Output:
- // {
- // title: 'Eloquent JavaScript',
- // language: 'English',
- // author: 'Marijn Haverbeke'
- // }
Promise.prototype.finally()
一開(kāi)始有兩個(gè)用于 Promise 的回調(diào)函數(shù)。其中一個(gè)是 then(),在實(shí)現(xiàn)諾 Promise 執(zhí)行。第二個(gè)是catch(),在 promise 被拒絕或 then() 拋出異常時(shí)執(zhí)行。ES2018 增加了用于 Promise 的第三個(gè)回調(diào)函數(shù) finally()。
每次完成 promise 時(shí),都會(huì)執(zhí)行 finally() 回調(diào),不管 promise 是否完成。這個(gè)回調(diào)的一般用于執(zhí)行應(yīng)始終發(fā)生的操作。例如關(guān)閉模態(tài)對(duì)話框、關(guān)閉數(shù)據(jù)庫(kù)連接或進(jìn)行某些清理。
- // finally() example:
- fetch()
- .then(response => response.json())
- .then(data => console.log(data))
- .catch(error => console.log(error))
- //最后做點(diǎn)什么:
- .finally(() => console.log('Operation done.'))
對(duì)正則表達(dá)式的改進(jìn)
ES2018 還對(duì)正則表達(dá)式功能進(jìn)行了的一些改進(jìn)。這些改進(jìn)包括 s(dotAll) 標(biāo)志,后行斷言,命名捕獲組和 unicode 屬性轉(zhuǎn)義。
s(dotAll)
首先是 s(dotAll) 。與點(diǎn)(.)不同,s(dotAll) 允許對(duì)換行符及表情符號(hào)進(jìn)行匹配。
- // s(dotAll) example:
- /hello.world/.test('hello\nworld')
- // Output:
- // false
- /hello.world/s.test('hello\nworld')
- // Output:
- // true
后行斷言
在ES2018之前,JavaScript僅支持先行斷言。先行斷言用于基于其后的文本來(lái)匹配模式。在 ES2018 中增加了對(duì)后行斷言的支持。通過(guò)它可以基于模式之前的文本模式來(lái)進(jìn)行匹配。后行斷言的語(yǔ)法為 ?<=。
- // 后行斷言例子:
- /(?<=green) apple/.test('One red apple is on the table.')
- // Output:
- // false
- /(?<=green) apple/.test('One green apple is on the table.')
- // Output:
- // true
斷言后面也有一個(gè)反向的回溯。僅當(dāng)子字符串之前沒(méi)有斷言時(shí),此斷言才與模式匹配。對(duì)后行斷言取反操作的語(yǔ)法是 ?<!。
- /(?<!green) apple/.test('One red apple is on the table.')
- // Output:
- // true
- /(?<!green) apple/.test('One green apple is on the table.')
- // Output:
- // false
命名捕獲組
另一個(gè)被 ES2018 引入到正則表達(dá)式的好功能是命名捕獲組。命名捕獲組的語(yǔ)法為 ?<some_name>。
- const date_pattern = /(?<day>\d{2})\/(?<month>\d{2})\/(?<year>\d{4})/
- const result = date_pattern.exec('11/12/2021')
- console.log(result)
- // Output:
- // [
- // '11/12/2021',
- // '11',
- // '12',
- // '2021',
- // index: 0,
- // input: '11/12/2021',
- // groups: [Object: null prototype] { day: '11', month: '12', year: '2021' }
- // ]
- console.log(result.groups.day)
- // Output:
- // '11'
- console.log(result.groups.month)
- // Output:
- // '12'
- console.log(result.groups.year)
- // Output:
- // '2021'
Unicode 屬性轉(zhuǎn)義
每個(gè) unicode 字符都有許多屬性。例如:空白字符,大小寫,字母,ASCII,表情符號(hào)等?,F(xiàn)在你可以在正則表達(dá)式中訪問(wèn)這些屬性了。
要使用這個(gè)功能需要做兩件事。首先必須使用 /u 標(biāo)志。這個(gè)標(biāo)志告訴 JavaScript 你的字符串是一系列 Unicode 代碼點(diǎn)。第二是使用 \p{}。你要檢查的屬性位于大括號(hào)之間,反之則用 \P{}。
- // 用俄語(yǔ)創(chuàng)建一個(gè)字符串(西里爾字母):
- const myStrCyr = 'Доброе утро'
- //創(chuàng)建英文字符串(拉丁字母):
- const myStrLat = 'Good morning'
- //測(cè)試“ myStrCyr”是否包含西里爾字符:
- /\p{Script=Cyrillic}/u.test(myStrCyr) // true
- //測(cè)試“ myStrLat”是否包含西里爾字符:
- /\p{Script=Cyrillic}/u.test(myStrLat) // false
- // 測(cè)試“myStrLat” 是否包含西里爾字符:
- /\p{Script=Latin}/u.test(myStrCyr) // false
- // 測(cè)試“myStrLat” 是否包含拉丁語(yǔ)字符:
- /\p{Script=Latin}/u.test(myStrLat) // true