這篇是ECMAScript 2016、2017和2018中所有新特性的示例!
跟蹤JavaScript/">JavaScript (ECMAScript)中的新內容是很困難的,而且更難找到有用的程式碼示例。
因此,在本文中將介紹 ofollow,noindex" target="_blank">TC39(最終草案) 在ES2016、ES2017和ES2018中新增的已完成提案中列出的所有18個特性,並給出有用的示例。
1.Array.prototype.includes
include 是陣列上的一個簡單例項方法,可以輕鬆查詢陣列中是否有指定內容(包括 NaN)。
2.求冪操作符
像加法和減法這樣的數學運算分別有像 + 和 - 這樣運算子。與它們類似, **
運算子通常用於指數運算。在ECMAScript 2016中,引入了 **
代替 Math.pow。
1.Object.values()
Object.values()是一個類似於Object.keys()的新函式,但返回物件自身屬性的所有值,不包括原型鏈中的任何值。
2.Object.entries()
Object.entries()與Object.keys 類似,但它不是僅返回鍵,而是以陣列方式返回鍵和值。 這使得在迴圈中使用物件或將物件轉換為對映等操作變得非常簡單。
例一:
例二:
3.字串填充
在String.prototype中添加了兩個例項方法: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=*=*=*=*='
當我們想要在漂亮的列印顯示或終端列印進行對齊時,這非常有用。
3.1 padStart 例子:
在下面的例子中,有一個不同長度的數字列表。我們希望在“0”為追加符讓所有項長度都為10位,以便顯示,我們可以使用padStart(10, '0')輕鬆實現這一點。
3.2 padEnd 例子:
當我們列印多個不同長度的專案並想要右對齊它們時,padEnd非常有用。
下面的示例是關於padEnd、padStart和 Object.entries 的一個很好的實際示例:
const cars = { ':blue_car:BMW': '10', ':oncoming_automobile:Tesla': '5', ':oncoming_taxi:Lamborghini': '0' } Object.entries(cars).map(([name, count]) => { console.log(`${name.padEnd(20, ' -')}Count: ${count.padStart(3, '0')}`) }) // 列印 // :blue_car:BMW - - - - - - -Count: 010 // :oncoming_automobile:Tesla - - - - - -Count: 005 // :oncoming_taxi:Lamborghini - - -Count: 000
3.3 :warning: 注意padStart和padEnd 在Emojis和其他雙位元組字元上的使用
Emojis和其他雙位元組字元使用多個unicode位元組表示。所以padStart padEnd可能不會像預期的那樣工作!:warning:
例如:假設我們要墊達到10個字元的字串的心:heart:emoji。結果如下:
'heart'.padStart(10, ":heart:"); // prints.. ':heart::heart:❤heart'
這是因為 :heart: 長2個位元組(' u2764 uFE0F')! 單詞 heart 是5個字元,所以我們只剩下5個字元來填充。 所以 JS 使用 ('u2764uFE0F' ) 填充兩顆心並生成 :heart::heart:。 對於最後一個,它只使用 ('u2764uFE0F' ) 的第一個位元組(u2764)來生成,所以是 ❤;
4.Object.getOwnPropertyDescriptors
此方法返回給定物件的所有屬性的所有屬性(包括getter setter set方法),新增這個的主要目的是允許淺 拷貝/克隆到另一個物件中的物件,類似 bject.assign。
Object.assign 淺拷貝除原始物件的 getter 和 setter 方法之外的所有屬性。
下面的示例顯示了 Object.assign 和 Object.getOwnPropertyDescriptors 以及Object.defineProperties 之間的區別,以將原始物件 Car 複製到新物件 ElectricCar 中。 可以看到使用 Object.getOwnPropertyDescriptors,discount 的 getter 和 setter 函式也被複制到目標物件中。
使用 Object.defineProperties
var Car = { name: 'BMW', price: 1000000, set discount(x) { this.d = x; }, get discount() { return this.d; }, }; console.log(Object.getOwnPropertyDescriptor(Car, 'discount')); // 列印 // { //get: [Function: get], //set: [Function: set], //enumerable: true, //configurable: true // } // 使用 Object.assign 拷貝物件 const ElectricCar = Object.assign({}, Car); //Print details of ElectricCar object's 'discount' property console.log(Object.getOwnPropertyDescriptor(ElectricCar, 'discount')); // 列印 // { //value: undefined, //writable: true, //enumerable: true, //configurable: true // } // //:warning:請注意,“discount” 屬性的 ElectricCar 物件中缺少getter和setter!:-1::-1: //Copy Car's properties to ElectricCar2 using Object.defineProperties //and extract Car's properties using Object.getOwnPropertyDescriptors const ElectricCar2 = Object.defineProperties({}, Object.getOwnPropertyDescriptors(Car)); //Print details of ElectricCar2 object's 'discount' property console.log(Object.getOwnPropertyDescriptor(ElectricCar2, 'discount')); //prints.. // { get: [Function: get],:point_left|type_3::point_left|type_3::point_left|type_3: //set: [Function: set],:point_left|type_3::point_left|type_3::point_left|type_3: //enumerable: true, //configurable: true // } // 請注意,在ElectricCar2物件中存在“discount”屬性的getter和setter !
5.函式引數的尾逗號
ES2017允許函式的最後一個引數有尾逗號(trailing comma), 此前,函式定義和呼叫時,都不允許最後一個引數後面出現逗號。這一變化將鼓勵開發人員停止醜陋的“行以逗號開頭”的習慣。這對於版本管理系統來說,就會顯示新增逗號的那一行也發生了變動。
6.Async/Await
到目前為止,個人感受是這是最重要和最有用的功能。 async 函式允許我們不處理回撥地獄,並使整個程式碼看起來很簡單。
async 關鍵字告訴 JavaScript 編譯器以不同的方式對待函式。每當編譯器到達函式中的 await 關鍵字時,它就會暫停。它假定 wait 之後的表示式返回一個 promise ,並在進一步移動之前等待該 promise 被 resolved 或 rejected。
在下面的示例中,getAmount 函式呼叫兩個非同步函式getUser和getBankBalance。使用 async await更加優雅和簡單達到有有序的呼叫 getUser 與 getBankBalance。
6.1.async 函式預設返回一個 promise
如果您正在等待 async 函式的結果,則需要使用 Promise 的 then 語法來捕獲其結果。
在以下示例中,我們希望使用 console.log 來列印結果但是不在 doubleAndAdd 函式裡面操作。 因為 async 返回是一個 promise 物件,所以可以在 then 裡面執行我們一些列印操作。
6.2 並行呼叫 async/await
在前面的例子中,我們呼叫doubleAfterlSec ,但每次我們等待一秒鐘(總共2秒)。 相反,我們可以使用 Promise.all 將它並行化為一個並且互不依賴於。
6.3 async/await 函式對錯誤的處理
在使用async/wait時,有多種方法可以處理錯誤。
方法一:在函式內使用 try catch
async function doubleAndAdd(a, b) { try { a = await doubleAfter1Sec(a); b = await doubleAfter1Sec(b); } catch (e) { return NaN; //return something } return a + b; } 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); }); }
方法二:在 await 後使用 catch 捕獲錯誤
// 方法二:在 await 後使用 catch 獲取錯誤 async function doubleAndAdd(a, b) { a = await doubleAfter1Sec(a).catch(e => console.log('"a" is NaN')); // :point_left: b = await doubleAfter1Sec(b).catch(e => console.log('"b" is NaN')); // :point_left: if (!a || !b) { return NaN; } return a + b; } doubleAndAdd('one', 2).then(console.log); // NaNand 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); }); }
方法三:在整個的 async-await 函式捕獲錯誤
//方法三:在整個的 async-await 函式捕獲錯誤 async function doubleAndAdd(a, b) { a = await doubleAfter1Sec(a); b = await doubleAfter1Sec(b); return a + b; } doubleAndAdd('one', 2) .then(console.log) .catch(console.log); // :point_left::point_left|type_3:<------- use "catch" function doubleAfter1Sec(param) { return new Promise((resolve, reject) => { setTimeout(function() { let val = param * 2; isNaN(val) ? reject(NaN) : resolve(val); }, 1000); }); }
1.共享記憶體 和 Atomics
這是一個巨大的、相當高階的特性,是JS引擎的核心增強。
其主要原理是在 JavaScript 中引入某種多執行緒特性,以便JS開發人員將來可以通過允許自己管理記憶體而不是讓 JS 引擎管理記憶體來編寫高效能的併發程式。
這是通過一種名為 SharedArrayBuffer (即 共享陣列緩衝區) 的新型別的全域性物件實現的,該物件本質上是將資料儲存在共享記憶體空間中。因此,這些資料可以在主JS執行緒和web工作執行緒之間共享。
到目前為止,如果我們想在主 JS 執行緒和 web 工作者之間共享資料,我們必須複製資料並使用postMessage 將其傳送到另一個執行緒。
你只需使用SharedArrayBuffer,資料就可以立即被主執行緒和多個web工作執行緒訪問。workers 之間的協調變得更簡單和更快(與 postMessage() 相比)。
但是線上程之間共享記憶體會導致競爭條件。為了幫助避免競爭條件,引入了 “Atomics” 全域性物件。 Atomics 提供了各種方法來線上程使用其資料時鎖定共享記憶體。 它還提供了安全地更新該共享記憶體中的搜尋資料的方法。
如果你這對個感興趣,可以閱讀以下文章:
- ES2017 新特性:共享記憶體 和 Atomics
- From Workers to Shared Memory — lucasfcosta
- A cartoon intro to SharedArrayBuffers — Lin Clark
- Shared memory and atomics — Dr. Axel Rauschmayer
2. Tagged Template literal restriction removed
首先,我們需要知道的什麼是 Template litera ls(“標記的模板文字”),以便更好地理解這個特性。Template literals是一個ES2015特性,它使用反引號包含一個字串字面量,並且支援嵌入表示式和換行,如:
下面的例子顯示,我們的自定義“Tag” 函式 greet 添加了一天中的時間,比如“Good Morning!” “Good afternoon” 等等,取決於一天中的時間字串的文字和返回自定義字串。
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()}`; //<-- 追加 Good morning/afternoon/evening here } }); return str; } const firstName = 'Raja'; const greetings = greet`Hello ${firstName}!`; //:point_left|type_3:<-- Tagged literal console.log(greetings); //'HelloRaja! Good Morning!' :fire: function timeGreet() { const hr = new Date().getHours(); return hr < 12 ? 'Good Morning!' : hr < 18 ? 'Good Afternoon!' : 'Good Evening!'; }
現在我們討論了什麼是“標記”函式,許多人希望在不同的領域中使用這個特性,比如在Terminal中用於命令,在組成 uri 的 HTTP 請求中,等等。
:warning: 帶標記字串文字的問題
問題是ES2015和ES2016規範不允許使用像“u”(unicode)、“x”(十六進位制)這樣的轉義字元,除非它們看起來完全像“ u00A9”或u{2F804}或xA9。
因此,如果你有一個內部使用其他域規則(如終端規則)的標記函式,可能需要使用看起來不像 u0049或 u {@ F804}的 ubla123abla,那麼你會得到一個語法錯誤,
function myTagFunc(str) { return { "cooked": "undefined", "raw": str.raw[0] } } var str = myTagFunc `hi \ubla123abla`; //call myTagFunc str // { cooked: "undefined", raw: "hi \\unicode" }
3.用於正則表示式的“dotall”標誌
目前在正則表示式中,雖然點(“.”)應該匹配單個字元,但它不匹配像 n r f 等新行字元。
例如:
//Before /first.second/.test('first\nsecond'); //false
這種增強使 點 運算子能夠匹配任何單個字元。為了確保它不會破壞任何東西,我們需要在建立正則表示式時使用s標誌。
//ECMAScript 2018 /first.second/s.test('first\nsecond'); //trueNotice: /s :point_left|type_3:
更多的方法,請看 這裡 :
4.RegExp Named Group Captures
這種增強 RegExp特性借鑑於像Python、Java等其他語言,因此稱為“命名組”。這個特性允許編寫開發人員以(<name>…)格式為 RegExp 中的組的不同部分提供名稱(識別符號),使用可以用這個名稱輕鬆地獲取他們需要的任何組。
4.1 Named group 的基礎用法
在下面的示例中,我們使用 (?<year>) (?<month>) 和 (?<day>) 名稱對日期正則表示式的不同部分進行分組。結果物件現在將包含一個groups屬性,該屬性具有 year、month和 day 的相應值。
let re1 = /(\d{4})-(\d{2})-(\d{2})/; let result1 = re1.exec('2015-01-02'); console.log(result1); // [ '2015-01-02', '2015', '01', '02', index: 0, input: '2015-01-02' ] // ECMAScript 2018 let re2 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u; let result2 = re2.exec('2015-01-02'); console.log(result2); // ["2015-01-02", "2015", "01", "02", index: 0, input: "2015-01-02", //groups: {year: "2015", month: "01", day: "02"} // ] console.log(result2.groups.year); // 2015
4.2 在 regex 內使用 Named groups
使用 \k<組名>
格式來反向引用正則表示式本身中的組,例如:
// 在下面的例子中,我們有一個包合的“水果”組。 // 它既可以配“蘋果”,也可以配“橘子”, // 我們可以使用 “\k<group name>” (\k<fruit>) 來反向引用這個組的結果, // 所以它可以匹配“=”相同的單詞 let sameWords = /(?<fruit>apple|orange)=\k<fruit>/u; sameWords.test('apple=apple') // true sameWords.test('orange=orange') // true sameWords.test('apple=orange') // false
4.3 在 String.prototype.replace 中使用 named groups
在 String.prototype.replace 方法中使用 named groups。所以我們能更快捷的交換詞。
例如,把 “firstName, lastName” 改成 “lastName, firstName”。
let re = /(?<firstName>[A-Za-z]+) (?<lastName>[A-Za-z]+$)/u; 'Hello World'.replace(re, `$<lastName>, $<firstName>`) // "World, Hello"
5.物件的 Rest 屬性
rest操作 …(三個點)允許挑練我們需要的屬性。
5.1 通過 Rest 解構你需要的屬性
let { firstName, age, ...remaining } = { firstName: '王', lastName: '智藝', age: 27, height: '1.78', race: '黃' } firstName; // 王 age; // 27 remaining; // { lastName: "智藝", height: "1.78", race: "黃" }
6.物件的擴充套件屬性
擴充套件 和 解析 的 三個點是一樣的,但是不同的是你可以用 擴充套件
去新建或者組合一個新物件。
擴充套件
是對齊賦值的右運算子, 而 解構
是左運算子。
const person = { fName: '小明', age: 20 }; const account = { name: '小智', amount: '$1000'}; const personAndAccount = { ...person, ...account }; personAndAccount; // {fName: "小明", age: 20, name: "小智", amount: "$1000"}
7.正則表示式反向(lookbehind)斷言
斷言(Assertion)是一個對當前匹配位置之前或之後的字元的測試, 它不會實際消耗任何字元,所以斷言也被稱為“非消耗性匹配”或“非獲取匹配”。
正則表示式的斷言一共有 4 種形式:
- (?=pattern) 零寬正向肯定斷言(zero-width positive lookahead assertion)
- (?!pattern) 零寬正向否定斷言(zero-width negative lookahead assertion)
- (?<=pattern) 零寬反向肯定斷言(zero-width positive lookbehind assertion)
- (?<!pattern) 零寬反向否定斷言(zero-width negative lookbehind assertion)
你可以使用組(?<=…) 去正向斷言,也可以用 (?<!…) 去取反。
正向斷言: 我們想確保 # 在 winning 之前。(就是#winning),想正則匹配返回 winning。下面是寫法:
反向斷言:匹配一個數字,有 € 字元而沒有 $ 字元在前面的數字。
更多內容可以參考: S2018 新特徵之:正則表示式反向(lookbehind)斷言
8. RegExp Unicode Property Escapes
用正則去匹配 Unicode 字元是很不容易的。像 w , W , d 這種只能匹配英文字元和數字。但是其他語言的字元怎麼辦呢,比如印度語,希臘語?
例如 Unicode 資料庫組裡把所有的印度語字元,標識為 Script = Devanagari。還有一個屬性 Script_Extensions, 值也為 Devanagari。 所以我們可以通過搜尋 Script=Devanagari,得到所有的印度語。
Devanagari 可以用於印度的各種語言,如Marathi, Hindi, Sanskrit。
在 ECMAScript 2018 裡, 我們可以使用 p 和 {Script=Devanagari} 匹配那些所有的印度語字元。也就是說 p{Script=Devanagari} 這樣就可以匹配。
//The following matches multiple hindi character /^\p{Script=Devanagari}+$/u.test('हिन्दी'); //true //PS:there are 3 hindi characters h
同理,希臘語的語言是 Script_Extensions 和 Script 的值等於 Greek 。也就是用Script_Extensions=Greek or Script=Greek 這樣就可以匹配所有的希臘語,也就是說,我們用 p{Script=Greek} 匹配所有的希臘語。
進一步說,Unicode 表情庫裡存了各式各樣的布林值,像 Emoji, Emoji_Component, Emoji_Presentation, Emoji_Modifier, and Emoji_Modifier_Base
的值,都等於 true。所以我們想搜 Emoji 等於 ture,就能搜到所有的表情。
我們用 \p{Emoji} ,\Emoji_Modifier
匹配所有的表情。
參考文獻:
8. Promise.prototype.finally()
finally()
是 Promise 新增的一個例項方法。意圖是允許在 resolve/reject 之後執行回撥。finally 沒有返回值,始終會被執行。
讓我們看看各種情況。
9.非同步迭代(Asynchronous Iteration)
這是一個極其好用的新特性。讓我們能夠非常容易的建立非同步迴圈程式碼。
原文: Here are examples of everything new in ECMAScript 2016, 2017, and 2018