很難追蹤 JavaScript(ECMAScript)中的新功能。 想找到有用的代碼示例更加困難。html
所以,在本文中,我將介紹 TC39 已完成 ES2016,ES2017 和 ES2018(最終草案)提案中全部添加的 18 個功能,並展現有用的示例。前端
這是一個很長的文章,但應該很容易閱讀。 能夠把它想象成 「Netflix binge reading」。可是到文章結束,我保證你將對全部這些功能有很好的瞭解。git
好的,讓咱們逐個討論這些問題。github
includes
是 Array 上的一個簡單實例方法,有助於輕鬆查找某項元素是否在數組中(包括NaN
,與 indexOf
不一樣)。web
人們想要 contains
來命名該規範,但顯然 Mootools 已經使用過這個命名,因此使用了 includes
。正則表達式
更多示例和常見問題請參見:ES2016 新特性:npm scripts : 每一個前端開發都應知道的一些使用提示數據庫
加法和減法等數學運算分別具備 +
和 -
等中綴運算符。與它們相似,**
中綴運算符一般用於指數運算。在 ECMAScript 2016 中,引入了 **
代替 Math.pow
。express
更多示例和常見問題請參見:ES2016 新特性:求冪運算符(**)npm
Object.values()
是一個與 Object.keys()
相似的新函數,但返回 Object 自身屬性的全部值,不包括原型鏈中的任何值。數組
更多示例和常見問題請參見:ES2017 新特性:Object.entries() 和 Object.values()
Object.entries()
與 Object.keys
相關,但它不只僅返回 keys ,而是以數組方式返回 keys 和 values 。這使得在循環中使用對象或將對象轉換爲 Maps 等操做變得很是簡單。
示例1
示例2
更多示例和常見問題請參見:ES2017 新特性:Object.entries() 和 Object.values()
String 中添加了兩個實例方法,String.prototype.padStart
和 String.prototype.padEnd
– 容許將空字符串或其餘字符串附加到原始字符串的開頭或結尾。
'someString'.padStart(numberOfCharcters [,stringForPadding]); '5'.padStart(10) // ' 5' '5'.padStart(10, '=*') //'=*=*=*=*=5' '5'.padEnd(10) // '5 ' '5'.padEnd(10, '=*') //'5=*=*=*=*='
當咱們想要對齊字符串的長度的時候,能夠很是方便的使用這兩個函數。
在下面的示例中,咱們列出了不一樣長度的數字。咱們但願前置「0」,以便全部項具備相同的 10 位數長度顯示。咱們可使用 padStart(10, '0')
輕鬆實現這一目標。
當咱們打印不一樣長度的多個項並但願正確對齊它們時,padEnd 真的很方便。
下面的示例是 padEnd
,padStart
和 Object.entries
組合在一塊兒以產生漂亮輸出的一個很好的現實示例。
const cars = { '🚙BMW': '10', '🚘Tesla': '5', '🚖Lamborghini': '0' } Object.entries(cars).map(([name, count]) => { //padEnd appends ' -' until the name becomes 20 characters //padStart prepends '0' until the count becomes 3 characters. console.log(`${name.padEnd(20, ' -')} Count: ${count.padStart(3, '0')}`) }); //Prints.. // 🚙BMW - - - - - - - Count: 010 // 🚘Tesla - - - - - - Count: 005 // 🚖Lamborghini - - - Count: 000
Emojis 和其餘雙字節字符使用多個字節的 unicode 表示。 因此 padStart
和 padEnd
可能沒法按預期工做!⚠️
例如:假設咱們把字符串 heart
經過 emoji表情 ❤️ 使用 padStart
延長到十個字節,這個時候咱們獲得以下的輸出:
這是由於 ❤️ 自己佔據兩個字節(\u2764\uFE0F
),而 heart
自己有 5 個字節,因此咱們只剩 5 個字節的位置能夠填充,JS使用\u2764\uFE0F
來填充兩顆心併產生 ❤️❤️ 。對於最後一個,它只使用 heart \u2764
的第一個字節產生 ❤
因此咱們最終獲得:❤️❤️❤heart
PS:你可使用 此連接 查看 unicode 字符轉換。
更多示例和常見問題請參見:ES2017 新特性:字符串方法:padStart 和 padEnd
此方法返回給定對象的全部屬性的全部詳細信息(包括 getter get
和 setter set
方法)。 添加它的主要動機是容許淺複製/克隆對象到另外一個對象,該對象也複製 getter 和 setter 函數而不像 Object.assign
。
Object.assign
淺複製除原始源對象的 getter
和 setter
函數以外的全部信息。
下面的示例顯示了 Object.assign
和 Object.getOwnPropertyDescriptors
以及 Object.defineProperties
之間的區別,以將原始對象 Car
複製到新對象 ElectricCar
中。 你將看到,經過使用 Object.getOwnPropertyDescriptors
,discount
getter 和 setter 函數也會複製到目標對象中。
以前…
之後…
var Car = { name: 'BMW', price: 1000000, set discount(x) { this.d = x; }, get discount() { return this.d; }, }; // 打印 Car 對象 'discount' 屬性的詳細信息 console.log(Object.getOwnPropertyDescriptor(Car, 'discount')); // 打印 .. // { // get: [Function: get], // set: [Function: set], // enumerable: true, // configurable: true // } //使用 Object.assign 將 Car 的屬性複製到 ElectricCar const ElectricCar = Object.assign({}, Car); // 打印 ElectricCar 對象 'discount' 屬性的詳細信息 console.log(Object.getOwnPropertyDescriptor(ElectricCar, 'discount')); // 打印 .. // { // value: undefined, // writable: true, // enumerable: true, // configurable: true // } //⚠️ 請注意,ElectricCar 對象中的 'discount' 屬性缺乏 getter 和 setter !👎👎 // 使用 Object.defineProperties 將 Car 的屬性複製到 ElectricCar2 , // 並使用 Object.getOwnPropertyDescriptors 提取 Car的屬性 const ElectricCar2 = Object.defineProperties({}, Object.getOwnPropertyDescriptors(Car)); // 打印 ElectricCar2 對象的 'discount' 屬性的詳細信息 console.log(Object.getOwnPropertyDescriptor(ElectricCar2, 'discount')); // 打印.. // { get: [Function: get], 👈🏼👈🏼👈🏼 // set: [Function: set], 👈🏼👈🏼👈🏼 // enumerable: true, // configurable: true // } // 請注意,ElectricCar2 對象中的 'discount' 屬性存在 getter 和 setter !
更多示例和常見問題請參見:ES2017 新特性:Object.getOwnPropertyDescriptors()
這是一個小更新,容許咱們在函數最後一個參數後面有逗號。 爲何? 幫助使用像 git blame 這樣的工具,防止添加一個參數卻須要修改兩行代碼。
如下示例顯示了問題和解決方案。
注意:你也能夠在調用函數時使用尾逗號!
更多示例和常見問題請參見:ES2017 新特性:函數參數列表和調用尾逗號
到目前爲止,這個特性應該是目前爲止是最重要和最有用的功能。async 函數解決了回調地獄的問題,並使整個代碼看起來簡單。
async
關鍵字告訴 JavaScript 編譯器以不一樣方式處理函數。 只要到達該函數中的 await
關鍵字,編譯器就會暫停。 它假定 await
以後的表達式返回一個 promise
並等待,直到 promise 被 resolved 或被 rejected ,而後才繼續執行。
在下面的示例中,getAmount
函數調用兩個異步函數 getUser
和 getBankBalance
。 咱們能夠用 Promise 作到這一點,可是使用 async await
更加優雅和簡單。
若是你正在等待 async 函數的結果,則須要使用 Promise 的 then
語法來捕獲其結果。
在如下示例中,咱們但願使用 console.log
但不在 doubleAndAdd
中記錄結果。 因此咱們想等待並使用 then
語法將結果傳遞給console.log
。
在前面的例子中,咱們調用 await 兩次,但每次咱們等待一秒鐘(總共2秒)。相反,咱們能夠並行調用它,由於使用 Promise.all
並行調用 a
和 b
。
使用 async/await 時,有多種方法能夠處理錯誤。
選項1-在函數中使用try catch
//Option 1 - Use try catch within the function async function doubleAndAdd(a, b) { try { a = await doubleAfter1Sec(a); b = await doubleAfter1Sec(b); } catch (e) { return NaN; //return something } return a + b; } //🚀Usage: doubleAndAdd('one', 2).then(console.log); // NaN doubleAndAdd(1, 2).then(console.log); // 6 function doubleAfter1Sec(param) { return new Promise((resolve, reject) => { setTimeout(function() { let val = param * 2; isNaN(val) ? reject(NaN) : resolve(val); }, 1000); }); }
選項2-捕獲(Catch) await 表達式
因爲每個 await
表達式返回的都是 Promise,咱們能夠直接在每一行上面添加 catch。
//Option 2 - *Catch* errors on every await line //as each await expression is a Promise in itself async function doubleAndAdd(a, b) { a = await doubleAfter1Sec(a).catch(e => console.log('"a" is NaN')); // 👈 b = await doubleAfter1Sec(b).catch(e => console.log('"b" is NaN')); // 👈 if (!a || !b) { return NaN; } return a + b; } //🚀Usage: doubleAndAdd('one', 2).then(console.log); // NaN and logs: "a" is NaN doubleAndAdd(1, 2).then(console.log); // 6 function doubleAfter1Sec(param) { return new Promise((resolve, reject) => { setTimeout(function() { let val = param * 2; isNaN(val) ? reject(NaN) : resolve(val); }, 1000); }); }
選項3-捕獲(Catch) 整個async-await函數
//Option 3 - Dont do anything but handle outside the function //since async / await returns a promise, we can catch the whole function's error async function doubleAndAdd(a, b) { a = await doubleAfter1Sec(a); b = await doubleAfter1Sec(b); return a + b; } //🚀Usage: doubleAndAdd('one', 2) .then(console.log) .catch(console.log); // 👈👈🏼<------- use "catch" function doubleAfter1Sec(param) { return new Promise((resolve, reject) => { setTimeout(function() { let val = param * 2; isNaN(val) ? reject(NaN) : resolve(val); }, 1000); }); }
這是一個巨大的,很是先進的功能,而且是對 JS 引擎的核心加強。
這個特性的主要目的是給 JavaScript 提供多線程功能,以便JS開發人員經過本身管理內存來編寫高性能的併發程序,而不是讓JS引擎管理內存。
這是經過一種名爲 SharedArrayBuffer 的新型全局對象完成的,該對象實質上將數據存儲在共享內存空間中。所以,這些數據能夠在主JS線程和 Web-worker 線程之間共享。
以前,若是咱們想在主 JS 線程和 web-worker 之間共享數據,咱們必須複製數據並使用 postMessage
將其發送到另外一個線程。
如今,你只需使用 SharedArrayBuffer
,主線程和多個 web-worker 線程均可以當即訪問數據。
可是在線程之間共享內存會致使競爭條件(即多個進程同時操做一個內存)。爲了幫助避免競爭條件,引入了 Atomics 全局對象。 Atomics 提供了各類方法來在線程使用其數據時鎖定共享內存。它還提供了安全地更新共享內存中的此類數據的方法。
建議經過某個庫使用此功能,可是如今沒有基於此功能構建的庫。
若是你有興趣,我建議閱讀:
首先,咱們須要澄清「標記模板字面量」是什麼,以便咱們更好地理解這個功能。
在 ES2015+ 中,有一個稱爲標記模板文字的功能,容許開發人員自定義字符串的插值方式。 例如,在標準方式中,字符串被插入以下…
在標記的字面量中,你能夠編寫一個函數來接收字符串字面量的硬編碼部分,例如 ['Hello','!']
而且替換變量,例如 ['Raja']
,做爲參數進入一個自定義函數(例如 greet
),並從該自定義函數返回任何你想要的內容。
下面的示例顯示咱們的自定義 「Tag」 函數 greet
,如「Good Morning」 「Good afternoon」,等等,取決於當天到字符串字面量的時間,並返回自定義字符串。
//A "Tag" function returns a custom string literal. //In this example, greet calls timeGreet() to append Good //Morning/Afternoon/Evening depending on the time of the day. function greet(hardCodedPartsArray, ...replacementPartsArray) { console.log(hardCodedPartsArray); //[ 'Hello ', '!' ] console.log(replacementPartsArray); //[ 'Raja' ] let str = ''; hardCodedPartsArray.forEach((string, i) => { if (i < replacementPartsArray.length) { str += `${string} ${replacementPartsArray[i] || ''}`; } else { str += `${string} ${timeGreet()}`; //<-- append Good morning/afternoon/evening here } }); return str; } //��Usage: const firstName = 'Raja'; const greetings = greet`Hello ${firstName}!`; //����<-- Tagged literal console.log(greetings); //'Hello Raja! Good Morning!' �� function timeGreet() { const hr = new Date().getHours(); return hr < 12 ? 'Good Morning!' : hr < 18 ? 'Good Afternoon!' : 'Good Evening!'; }
如今咱們討論了 「Tagged」 函數是什麼,許多人想要在不一樣的場景下中使用此功能,例如在終端中使用命令和 HTTP 請求來編寫 URI ,等等。
⚠️標籤字符串模版存在的問題
ES2015 和 ES2016 規範不容許使用轉義字符,如 \u
(unicode),\x
(十六進制),除非它們看起來徹底像 \u00A9
或 \u{2F804}
或 \xA9
。
所以,若是你有一個內部使用其餘域規則(如終端規則)的 Tagged 函數,可能須要使用 \ubla123abla
,而不能是 \u0049
或 \u{@F804}
,這樣你會獲得一個語法錯誤。
在 ES2018 中,只要 Tagged 函數返回具備 「cooked」 屬性(無效字符爲 「undefined」 )的對象中的值,而後是 「raw」 屬性( 不管你想要什麼)。
function myTagFunc(str) { return { "cooked": "undefined", "raw": str.raw[0] } } var str = myTagFunc `hi \ubla123abla`; //call myTagFunc str // { cooked: "undefined", raw: "hi \\unicode" }
目前在RegEx中,點(「.」)能夠表示任何的單一字符,但它不能與 \n
, \r
,\f
等換行符匹配。 例如:
//Before /first.second/.test('first\nsecond'); //false
此加強功能使點運算符能夠匹配任何單個字符。爲了確保不會破壞任何內容,咱們須要在建立RegEx時使用 \s
標記才能使其正常工做。
//ECMAScript 2018 /first.second/s.test('first\nsecond'); //true Notice: /s 👈🏼
如下是 提案 文檔中的總體API:
這個加強功能帶來了其餘語言(如Python,Java等)的有用 RegExp 功能,稱爲「命名組」。這個功能容許容許正則表達式給每個捕獲組起一個名字 (?<name>...)
,而後,咱們可使用該名稱輕鬆獲取咱們須要的任何羣組。
在下面的例子中,咱們使用 (?<year>)
(?<month>)
(?<day>)
來爲正則表達式中的不一樣部分分組,結果對象中會包含一個 groups
屬性,其擁有 year
month
day
三個對象。
咱們可使用 \k<group name>
格式來反向引用正則表達式自己中的組。如下示例顯示了它的工做原理。
命名組也能夠在 String 的 replace 方法中使用,好比用來交換一個字符串中各個部分的位置。
例如,將firstName, lastName
更改成 lastName, firstName
。
Rest 運算符 ...
(三個點)容許咱們提取 Object 的剩餘屬性。
展開屬性看起來就像 Rest 運算符,都是三個點 ...
,但不一樣之處在於你使用展開操做符來建立(重構)新對象。
提示:展開(spread)運算符用於等號的右側。剩餘(Rest)運算符用在等號的左側。
這是 RegEx 的一個加強,它容許咱們確保某些子字符串剛好出如今某些子字符串以前。
你如今可使用一個組 (?<=…)
(問號,小於,等於)來查看先行斷言。
此外,你可使用 (?<!…)
(問號,小於,感嘆號)來查看後行斷言。基本上,只要-ve斷言經過,這將匹配。
確定斷言:假設咱們要確保 #
符號存在於 winning
以前(即:#winning
),並但願正則表達式只返回字符串 「winning」 。下面是咱們的作法:
否認斷言:假設咱們想要從具備 €
符號的行中提取數字,而不是 $
。
提案連接:https://github.com/tc39/proposal-regexp-unicode-property-escapes
編寫 RegEx 以匹配各類 unicode 字符並不容易。像 \w
,\W
,\d
等只匹配英文字符和數字。可是其餘語言中的數字如印地語,希臘語等等該怎麼辦呢?
這就是 Unicode 屬性轉義的用武之地。事實證實,Unicode 爲每一個符號(字符)添加元數據屬性,並使用它來分組或表徵各類符號。
例如,Unicode 數據庫將全部印地語字符(??????)歸爲一個名爲 Script
的屬性,其值爲 Devanagari
,另外一個屬性爲Script_Extensions
,其值爲 Devanagari
。因此咱們能夠搜索 Script=Devanagari
並得到全部印地語字符。
梵文能夠用於各類印度語言,如馬拉地語,印地語,梵語等。
從 ECMAScript 2018 開始,咱們可使用 \p
來轉義字符以及 {Script = Devanagari}
以匹配全部這些印度字符。也就是說,咱們能夠在 RegEx 中使用:\p{Script=Devanagari}
來匹配全部梵文字符。
一樣,Unicode 數據庫將 Script_Extensions
(和 Script
)屬性下的全部希臘字符組合爲希臘語。 因此咱們可使用 Script_Extensions=Greek
或 Script=Greek
搜索全部希臘字符。
也就是說,咱們能夠在RegEx中使用: \p{Script=Greek}
來匹配全部希臘字符。
此外,Unicode數據庫在布爾屬性 Emoji
,Emoji_Component
, Emoji_Presentation
,Emoji_Modifier
和 Emoji_Modifier_Base
下存儲各類類型的 Emojis,其屬性值爲 true
。 所以,咱們只需選擇 Emoji 符號便可搜索全部表情符號。
也就是說,咱們可使用:\p{Emoji}
,\Emoji_Modifier
等來匹配各類 Emojis 。
如下示例將使一切清楚。
//The following matches an Emoji character /\p{Emoji}/u.test('❤️'); //true //The following fails because yellow emojis don't need/have Emoji_Modifier! /\p{Emoji}\p{Emoji_Modifier}/u.test('✌️'); //false //The following matches an emoji character\p{Emoji} followed by \p{Emoji_Modifier} /\p{Emoji}\p{Emoji_Modifier}/u.test('✌🏽'); //true //Explaination: //By default the victory emoji is yellow color. //If we use a brown, black or other variations of the same emoji, they are considered //as variations of the original Emoji and are represented using two unicode characters. //One for the original emoji, followed by another unicode character for the color. // //So in the below example, although we only see a single brown victory emoji, //it actually uses two unicode characters, one for the emoji and another // for the brown color. // //In Unicode database, these colors have Emoji_Modifier property. //So we need to use both \p{Emoji} and \p{Emoji_Modifier} to properly and //completely match the brown emoji. /\p{Emoji}\p{Emoji_Modifier}/u.test('✌🏽'); //true
最後,咱們可使用大寫「P」( \P
)轉義字符,而不是小寫「p」( \p
)來否認匹配。
參考:
finally()
是一個添加到 Promise 實例的新方法。 主要考慮是容許在 resolve 或 reject 調用以後執行一些清理性質的代碼。finally 被執行的時候不會被傳入任何函數,而且不管何時都會被執行。
咱們來看看各類狀況。
這是一個很是有用的特性。 基本上它容許咱們輕鬆建立異步代碼循環!
此特性添加了一個新的「for-await-of」循環,容許咱們在循環中調用返回 promises(或帶有一堆 promise 的 Arrays )的異步函數。 循環會等待每一個 Promise 在進行下一個循環以前 resolve 。