原本想稍微整理一下 ES 新特性,沒想到花了相當多的時間,本文也巨長,依然推薦使用 簡悅 生成目錄。
原文竟然由于過長無法發(fā)布,第一次知道簡書還有文章字數限制?,F在只能拆成兩篇發(fā)布。
本系列文章
ES6~ES11 特性介紹之 ES6 篇
ES6~ES11 特性介紹之 ES7~ES11 篇
特性列表
ES7(ES 2016) 特性
2016 年 6 月推出的 ES2016 規(guī)范也就是 ES7 只做了微量的更新,添加了兩個特性:
- Array.prototype.includes
- 指數操作符
#01 Array.prototype.includes()
在上文 ES6 部分的 6.3.3 includes(), startsWith(), endsWith(), repeat() 一節(jié)中,我們介紹了字符串的 includes 方法來判斷是否包含指定的「參數字符串」。ES2016 則繼續(xù)補充了數組的 includes 方法,用來判斷數組中是否包含指定的值:
const arr = [1, 2, 3];
console.log(arr.includes(1)); // true
console.log(arr.includes(4)); // false
includes 方法可以傳入第二個參數,第二個參數表示「搜索的起始位置」,默認為 0:
const arr = [1, 2, 3];
console.log(arr.includes(1)); // true
console.log(arr.includes(1, 1)); // false。從 1 開始搜索,不存在元素 1
console.log(arr.includes(3, -1)); // true。負數表示倒數位置,-1 表示從倒數第一個元素開始,向后查找
在 ES2016 出現之前,我們通常使用 indexOf 來判斷數組中是否包含某個指定值。但 indexOf 在語義上不夠明確直觀,同時 indexOf 內部使用 === 來判等,所以存在對 NaN 的誤判,includes 則修復了這個問題:
const arr = [NaN];
console.log(arr.indexOf(NaN)); // -1。表示不存在
console.log(arr.includes(NaN)); // true。存在
#02 指數操作符
ES2016 引入了指數操作符 **,用來更為方便的進行指數計算,與 Math.pow() 等效:
console.log(Math.pow(2, 10)); // 1024。計算 2 的十次方
console.log(2 ** 10); // 1024。計算 2 的十次方
ES8(ES 2017) 特性
#01 async/await
在 ES6 部分的 08 Generator 函數 一節(jié)介紹了 Generator 函數,其中 8.5 在異步編程上的應用 小節(jié)則是介紹了 Generator 自動調度器。
而所謂的 async/await 其實就是實現一個自動調度的 Generator 函數的語法糖。
async 其實就是 Generator 函數聲明語句的語法糖,只是 async 函數多了一個限制,即返回值為 Promise 對象,即使在編碼時返回是各種各樣類型的值,但最后都會被封裝成一個 Promise 對象。
而 await 相當于 yield。之前提及過使用第三方自動調度器則 yield 后面需要跟 Thunk 函數或 Promise 對象。而 await 后面則是統一跟 Promise 對象,如果不是 await 也會自動將后面表達式值轉為 Promise 對象。
另一方面 async/await 在語義上也變得更為直觀和清晰。當然更重要的是 async/await 內部幫我們實現了 Generator 自動調度器。所以通過 async/await 我們將非常容易的實現一個自動調度的 Generator 函數:
async function asyncByAsync() {
const result1 = await req("/users"); // req 需要返回 Promise 對象,或者 req 也是一個 async
const result2 = await req("/admin"); // req 需要返回 Promise 對象,或者 req 也是一個 async
console.log(result1);
console.log(result2);
}
#02 對象擴展
2.1 Object.values 和 Object. entries
和 Object.keys 配套,ES2017 引入了 Object.values 獲取對象的所有值,以及 Object. entries 來獲取對象的所有鍵值對:
const obj = {a: 1, b: 2, c: 3};
for (const value of Object.values(obj)) {
console.log(value); // 1、2、3
}
for (const [key, value] of Object.entries(obj)) {
console.log(key, value); // a 1、b 2、c 3
}
2.2 Object.getOwnPropertyDescriptor
ES2017 之前已經存在 Object.getOwnPropertyDescriptor() 方法,用來獲取對象中某個屬性的描述對象。與此相對,ES2017 引入了 Object.getOwnPropertyDescriptors() 方法,用來獲取對象中所有自身屬性(非繼承屬性)的描述對象:
貼心小提示:兩個方法名稱非常像,不過第一個沒有 s,第二個有 s
const obj = {a: 1, b: 2, c: 3};
console.log(Object.getOwnPropertyDescriptors(obj));
// a: {value: 1, writable: true, enumerable: true, configurable: true}
// b: {value: 2, writable: true, enumerable: true, configurable: true}
// c: {value: 3, writable: true, enumerable: true, configurable: true}
#03 SharedArrayBuffer 和 Atomics
JavaScript 是單線程的,但 Web Worker 則是多線程的,Web Worker 多線程之間通過 postMessage、onmessage 等方式進行通信傳遞消息。但當數據量比較大時,直接傳遞數據效率較低。因此 ES2017 引入了 SharedArrayBuffer 來實現共享內存:
// 新建 1KB 共享內存
const sharedBuffer = new SharedArrayBuffer(1024);
// 主線程將共享內存的地址發(fā)送出去
w.postMessage(sharedBuffer);
// sharedBuffer 不能直接讀寫
// 只能通過視圖(TypedArray視圖和DataView視圖)來讀寫
// 視圖的作用是以指定格式解讀二進制數據。
// 在共享內存上建立視圖,方便讀寫
const sharedArray = new Int32Array(sharedBuffer);
// Worker 線程
onmessage = function (ev) {
// 主線程共享的數據
const sharedBuffer = ev.data;
// 在共享內存上建立視圖,供寫入數據
const sharedArray = new Int32Array(sharedBuffer);
// ...
};
引入共享內存就必然設計到多線程的競爭讀寫,那么最好是能夠封裝一些必要的「原子性操作」以供線程讀寫。Atomics 對象 并是提供「原子性操作」的角色。
值得注意的是提供了「原子性操作」并不意味著不再存在并發(fā)的資源競爭問題,有興趣的可以查閱LevelDB 中的跳表實現 中的 「并發(fā)處理」部分。
Atomics 對象 提供了 store()、load()、exchange() 等等方法。
#04 字符串擴展
ES2017 引入了兩個字符串補全函數 padStart()和padEnd()。如果某個字符串不夠指定長度,那么這兩個函數可以用指定字「填補」直至達到指定長度。兩個函數分別對應在字符串的頭部和尾部補全字符串:
// 第一個參數表示最大長度為 5,不夠則一直用 'ab' 填補在頭部
'x'.padStart(5, 'ab'); // 'ababx'
// 因為第一個參數指定最大長度為 4
// 第二次用 'ab' 填補時由于達到了 4,所以只用 ab 中的 a 填補
'x'.padStart(4, 'ab'); // "abax"
'x'.padEnd(5, 'ab'); // 'xabab'
// 如果指定長度小于字符串現有長度
// 則不做處理,返回原字符串
'xxxx'.padStart(3, 'ab'); // 'xxxx'
// 省略第二個參數
'x'.padStart(5); // ' x' 省略第二個參數,則默認使用空格 ' ' 填充
'x'.padEnd(5); // 'x '
#05 函數擴展
ES2017 規(guī)定函數的參數列表的結尾可以為逗號:
function Dog(
name,
age,
sex, // ES2017 之前非法,ES2017 后合法
) { /* 函數體 */ }
和對象最后屬性結尾可以為逗號的理由一樣,如果沒有該逗號,添加一個參數就需要修改兩行(一行需要加個逗號,一行加屬性),這在 git 多人協作時會干擾對本次修改的查看。
ES9(ES 2018) 特性
#01 異步迭代器
在上文 ES6 部分的 #11 Iterator 迭代器 一節(jié)中,我們已經介紹了 Iterator 迭代器的基本概念和用法:
const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator](); // 獲取迭代器
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: true}
但是上述代碼表達的迭代器是一種針對「同步數據」的「同步迭代」,無法實現對「異步數據」的迭代,而 ES2018 引入了對「異步迭代」的支持
重溫一下同步迭代器的三個概念:
-
Iterable: 一個數據結構只要部署了
Symbol.iterator 屬性,我們就可以稱之為Iterable即可迭代的。Symbol.iterator 屬性為一個函數,該函數應該返回本數據結構的迭代器Iterator 對象。 -
Iterator:通過
Symbol.iterator函數返回的對象即為用來訪問數據結構的Iterator 對象。該對象通常一開始指向數據結構的起始地址,同時具有next()函數,調用next()函數指向第一個元素,再次調用next()函數指向第二個元素.... 重復并可迭代數據結構中所有元素。 -
IteratorResult:
Iterator 對象每一次調用next()訪問的元素會被包裝返回,返回值為一個對象,其中包含value屬性表示值、done屬性表示是否已經迭代完成。
異步迭代器基本和同步迭代器基本一一對應:
-
Async Iterable: 與同步迭代器的
Symbol.iterator 屬性相對應,異步迭代器需要部署的是Symbol.asyncIterator屬性。 -
Iterator:與同步迭代器相對應,只不過其中的
next()函數返回的是對 IteratorResult 結果的Promise封裝。 -
IteratorResult:與同步迭代器相對應。只是不再被直接返回,而是封裝成了一個
Promise對象。
異步迭代器的使用:
// 自定義一個實現了 `Symbol.asyncIterator ` 屬性的 AsyncIterable 數據結構
const asyncIterable = new AsyncIterable(['a', 'b']);
// 得到異步迭代器對象
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
// next() 返回的是一個 promise 對象
asyncIterator.next()
.then(iterResult1 => { // 通過 .then 處理結果
// { value: 'a', done: false } // 返回的 IteratorResult 結果
console.log(iterResult1);
// 迭代下一個異步數據
return asyncIterator.next();
})
.then(iterResult2 => {
// { value: 'b', done: false }
console.log(iterResult2);
return asyncIterator.next();
})
.then(iterResult3 => {
// { value: undefined, done: true }
console.log(iterResult3);
});
當然還可以結合 async/await 簡化實現:
async function f() {
const asyncIterable = new AsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
// { value: 'a', done: false }
console.log(await asyncIterator.next());
// { value: 'b', done: false }
console.log(await asyncIterator.next());
// { value: undefined, done: true }
console.log(await asyncIterator.next());
}
之前也提及過同步迭代器可以使用 for...of 語句進行循環(huán),而對于異步迭代器則可以使用 for await...of 語句:
async function f() {
for await (const x of createAsyncIterable([1, 2])) {
console.log(x);
}
}
#02 Promise.prototype.finally()
ES2018 在 Promise 方面也做了一些補充,添加了 finally() 方法,表示無論 Promise 實例最終成功或失敗都會執(zhí)行的方法:
const promise = new Promise(function(resolve, reject) {
setTimeout(() => {
const one = '1';
reject(one);
}, 1000);
});
promise
.then(() => console.log('success'))
.catch(() => console.log('fail'))
.finally(() => console.log('finally'))
finally() 函數不接受參數,finally() 內部通常不知道 promise 實例的執(zhí)行結果,所以通常在 finally() 方法內執(zhí)行的是與 promise 狀態(tài)無關的操作。
#03 對象的 Rest/Spread
在 ES6 部分的 2.1.3 rest 操作符 ... 節(jié) 和 6.5.1 spread 擴展運算符與數組 節(jié)分別簡單介紹了 rest 運算符和 spread 運算符,如下所示:
// rest 運算符: 將元素組織成數組
const [a, ...b] = [1, 2, 3];
console.log(b); // 輸出 [2, 3]
(function(...arr) {
console.log(arr); // 輸出 [1, 2, 3]
}(1, 2, 3));
// spread 運算符:將數組擴展為元素
const arr = [1, 2, 3];
console.log(...arr); // 輸出 1 2 3
但是之前 rest/spread 運算符 只能作用于數組,ES2018 可以實現將其作用于對象:
// 1. rest 運算符: 將元素組織成對象
const obj = {a: 1, b: 2, c: 3};
const {a, ...rest} = obj;
console.log(rest); // 輸出 {b: 2, c: 3}
(function({a, ...obj}) {
console.log(obj); // 輸出 {b: 2, c: 3}
}({a: 1, b: 2, c: 3}));
// 2. spread 運算符:將對象擴展為元素
const obj = {a: 1, b: 2, c: 3};
const newObj ={...obj, d: 4};
console.log(newObj); // 輸出 {a: 1, b: 2, c: 3, d: 4}
// 可以用來合并對象
const obj1 = {a: 1, b:2};
const obj2 = {c: 3, d:4};
const mergedObj = {...obj1, ...obj2};
console.log(mergedObj); // 輸出 {a: 1, b: 2, c: 3, d: 4}
#04 正則表達式的擴展
4.1 正則表達式 dotAll 模式
正則表達式中,. 符號代表匹配任意單個字符。但特殊字符 \n、\r 等「行終止符」無法被 . 匹配。
ES2018 引入了 s 修飾符實現 dotAll 模式(. 真正匹配包含行終止符在內的任意單個字符):
const reg = /dog.age/;
reg.test('dog\nage'); // false。dot . 無法匹配 /n
const reg_dotall = /dog.age/s; // 通過 s 修飾符實現 dotAll 模式
reg_dotall.test('dog\nage'); // true
4.2 正則表達式后行斷言
在 ES2018 之前,JavaScript 正則表達式支持「先行斷言」和「先行否定斷言」。
-
先行斷言
x(?=pattern)
緊接x之后的字符應該匹配pattern,匹配上后返回的結果不包含匹配pattern的字符。由于pattern不消耗字符,所以這類斷言也被視為「零寬斷言」。
const reg = /aa(?=bb)/; // aa 后面緊跟 bb
reg.exec('aabb'); // 輸出 aa。aabb 符合上述模式,但匹配結果不會包含 bb 字符。
-
先行否定斷言
x(?!pattern)
緊接x之后的字符應該不匹配pattern:
const reg = /aa(?!bb)/; // aa 后面不能緊跟 bb
reg.exec('aabb'); // null
reg.exec('aab'); // 輸出 aa。aab 滿足上述模式,但注意匹配結果不會包含括號 () 里的內容
ES2018 補充了與先行相對應的「后行斷言」和「后行否定斷言」:
-
后行斷言
(?<=pattern)x
緊接x之前的字符應該匹配pattern:
const reg = /(?<=aa)bb/; // bb 前面緊接著 aa
reg.exec('aabb'); // 輸出 bb。aabb 滿足模式,但是匹配結果不包含括號內的內容即 aa
-
后行否定斷言
(?<!pattern)x
緊接x之前的字符應該不匹配pattern:
const reg = /(?<!aa)bb/; // bb 前面不能緊接著 aa
reg.exec('aabb'); // null
reg.exec('abb'); // 輸出 bb。bb 前面沒有緊接著 aa,滿足模式。但輸出結果不會包含 a
4.3 正則表達式 Unicode 屬性類
ES2018 引入了 \p{...} 和 \P{...} 來實現對 Unicode 屬性的指定。例如指定查找范圍為「希臘符號」,以前只能限定字符區(qū)間,現在可以直接指定屬性:
// \p 匹配滿足條件的數據
const reg1 = /\p{Script=Greek}/u; // 指定 Script 為 Greek,即只匹配「希臘字符」。
reg1.test('π') // true
// \P 與 \p 相反,表示匹配不滿足條件的數據
const reg2 = /\P{Script=Greek}/u; // 指定 Script 為 Greek,但 \P 表示匹配「非希臘字符」。
reg2.test('π') // false
4.4 正則表達式具名組匹配
在 ES2018 之前,我們可以通過 () 來實現分組:
const reg = /(\d{4})-(\d{2})-(\d{2})/;
const arr = reg.exec('2020-12-31');
const year = arr[1]; // 2020
const month = arr[2]; // 12
const day = arr[3]; // 31
上面的分組通過數組來對應,每個括號對應一個數組元素,通過下標來訪問。這種方式在語義上不夠清晰,在操作時也不夠直觀。所以 ES2018 引入了 ?<name> 實現給組進行命名:
// 在 \d{4} 前面添加 ?<year>,給該組命名為 year
// ?<month> 和 ?<day> 同理
const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const obj = reg.exec('2020-12-31');
// 直接通過 year 名稱獲取組匹配結果
const year = obj.groups.year; // 2020
const month = obj.groups.month; // 12
const day = obj.groups.day; // 31
#05 模板字符串的轉義限制放寬
在 ES6 的 6.3.1 模板字符串 一節(jié)中介紹了模板字符串和標簽模板,其中標簽模板默認會將字符串轉義:
// 報錯。\unicode 轉義失敗
const doc = latex`\newcommand{\unicode}{\textbf{Unicode!}}`;
上述代碼中的 \unicode 被當作 Unicode 字符進行轉義,從而導致錯誤。ES2018 對此的更新是「放寬」轉義限制。遇到無法轉義的字符時返回 undefined 而不是報錯,這樣可以使得程序繼續(xù)運行。同時函數可以從 6.3.1 模板字符串 一節(jié)中提及的第一個參數中的 raw 屬性拿到原始字符串。
但注意上述規(guī)則針對標簽模板,普通模板字符串遇到無法轉義的 Unicode 字符還是會報錯:
const str = `hello \unicode`;
ES10(ES 2019) 特性
#01 字符串擴展
1.1 U+2028 和 U+2029(JSON Superset)
在 ES2019 之前,JavaScript 的字符串中是不能直接包含一些特殊 Unicode 字符,例如 U+000D回車、U+000A 換行以及 U+2028 行分隔符 和 U+2029 段分隔符等。
但是由于 JSON 格式規(guī)范允許字符串里直接包含 U+2028 行分隔符 和 U+2029 段分隔符(非轉義形式),這樣將導致 JavaScript 在解析包含這兩種特殊字符的 JSON 串時出錯。
于是 ES2019 添加了字符串對這兩個字符的支持,可以在字符串中使用這兩種字符:
const str1 = eval("'\u2029'"); // 不報錯。解析轉義字符 \u2029
1.2 trimStart 和 trimEnd
ES2019 添加了 trimStart 和 trimEnd 這兩個新方法,分別用來去除字符串的首部和尾部空白字符:
const hiMessage = ` Hello Word! `;
console.log(hiMessage.trimStart()); // 'Hello Word! '
console.log(hiMessage.trimEnd()); // ' Hello Word!'
#02 數組擴展
ES2019 在數組類型上添加了 flat() 和 flatMap() 這兩個新方法。
- flat()
flat 函數用來將嵌套數組(多層次數組)「拉平」:
// [1, 2, 3, 4];
[1, 2, [3, 4]].flat();
// [1, 2, 3, 4, 5] 參數為層次設置,2 表示作用于內嵌 2 層
[1, 2, [3, [4, 5]]].flat(2);
// [1, 2, 3, 4, 5],Infinity 表示無論多少層,拉平所有層次
[1, 2, [3, [4, [6]]]].flat(Infinity);
-
flatMap()
flatMap函數對數組每個元素進行一些「處理」,然后處理返回的是一個數組,那么數組會被flat拉平,上述的「處理」由回調函數給出:
// 先對每個元素進行 ele * 2 ,并返回數組,即 [2], [4], [6], [8]
// 再對返回值應用 flat 函數拉平
// [[2], [4], [6], [8]].flat(),最終得到 [2, 4, 6, 8]
[1, 2, 3, 4].flatMap(ele => [ele * 2]); // [2, 4, 6, 8]
// 注意 flatMap 中 flat 只會拉平一層
[1, 2, 3, 4].flatMap(ele => [[ele * 2]]); // [[2], [4], [6], [8]]
#03 對象擴展
ES2019 在對象 Object 上添加了 fromEntries 方法,該方法相當于 Object.entries 方法的逆過程,用來將一個 key-value 「鍵值對列表」轉換成「對象」。
Object.fromEntries(iterable) 所接受的參數是像 Array、Map 那樣實現了 iterable 迭代協議(且迭代的每個元素包含 key\value 兩個數值)的數據結構:
// Array
const dog = [['name', 'wangwang'], ['age', 5]];
const obj1 = Object.fromEntries(dog);
console.log(obj1); // {name: "wangwang", age: 5}
// Map
const cat = new Map();
cat.set('name', 'miaomiao');
cat.set('age', 2);
const obj2 = Object.fromEntries(cat);
console.log(obj2); // {name: "miaomiao", age: 2}
#04 Symbol 的擴展
在 ES6 部分的 6.1 節(jié)介紹了 Symbol,其中知曉傳入的參數相當于「描述」:
let s1 = Symbol("dog"); // dog 為描述
在 ES2019 之前,獲取一個 Symbol 值的描述需要通過 String 方法 或 toString 方法:
console.log(String(s1)); // "Symbol(dog)"
console.log(s1.toString()); // "Symbol(dog)"
ES2019 補充了屬性 description,用來直接訪問「描述」:
console.log(s1.description); // 直接獲取到 dog
#05 函數的擴展
ES2019 對函數的 toString() 方法進行了擴展,以前這個方法只會輸出函數代碼,但會省略注釋和空格。ES2019 的 toString() 則會保留注釋、空格等,即輸出的是原汁原味的原始代碼:
function sayHi() {
/* dog dog */
console.log('wangwang.');
}
sayHi.toString(); // 將輸出和上面一樣的原始代碼
#06 JSON.stringify() 的優(yōu)化
JSON 數據需要為是 UTF-8 編碼,但在 ES2019 之前 JSON.stringify() 可能輸出不符合 UTF-8 規(guī)范的字符。
原因在于 UTF-8 標準中為了表示 oxFFFF 的字符,將 0xD800 到 0xDFFF 范圍內的編碼用來組合使用,不能單獨使用。此時將這個范圍內的編碼傳給 JSON.stringify() ,函數將返回亂碼:
JSON.stringify('\uD800'); // '"?"'。 ES2019 之前
ES2019 則對這種情況進行特殊處理,如果遇到無對應編碼結果的編碼,則直接返回轉義字符串:
JSON.stringify('\uD800'); // 返回 '"\\ud800"'。ES2019 之后
#07 無參 catch
ES2019 以前,catch 會帶有參數,而現在可以不帶參數:
// ES2019 之前
try {
...
} catch(error) {
...
}
// ES2019 之后
try {
...
} catch {
...
}
#08 Array.prototype.sort() 穩(wěn)定
ES2019 之前數組的 sort() 方法是否穩(wěn)定是不明確的,任由瀏覽器自己實現。ES2019 開始明確規(guī)定排序算法必須是穩(wěn)定的。
ES11(ES 2020) 特性
#01 模塊化擴展
1.1 動態(tài)導入 import()
在上文 ES6 部分的 04 模塊 Module 節(jié)中介紹了模塊相關的知識,其中模塊導入使用 import 命令實現,但是了解到 import 命令是一種靜態(tài)導入,所以在 ES2020 之前我們無法像 CommonJS 中的 require 那樣動態(tài)導入模塊。那么「條件導入」、「運行時確定模塊」等功能就會被限制。
ES2020 引入了 import() 操作符來實現動態(tài)導入:
const module = 'animal';
if (type === 1) {
module = 'dog';
} else if (type === 2) {
module = 'cat';
}
// 導入模塊的名稱可以動態(tài)計算,即運行時再確定
const utils = await import(module);
// 使用 .then 進行回調處理
import(module)
.then(module => module.sayHi())
.catch(error => console.log(error));
1.2 import.meta
ES2020 還引入了 import.meta 對象,其中包含了模塊的一些元信息,例如import.meta.url存儲了瀏覽器 URL 或文件名。
1.3 export * as module from "xxx";
在 ES6 中 4.3 導入導出混合寫法 一節(jié)中介紹了 import 和 export 混合用法,例如:
export * from 'my_module';
但是還有一種導入寫法沒有對應的混合寫法:
import * as module from "xxx";
ES2020 補充了上述語句對應的混合導出寫法:
export * as module from "xxx";
02 字符串擴展
2.1 matchAll
在正則表達式中,如果一個字符串有多個匹配,我們可以通過循環(huán)來依次獲?。?/p>
const reg = /dog/g;
const str = 'dog1dog2dog3dog4';
const dogs = [];
let dog;
// 循環(huán)調用 exec,依次獲取匹配結果
while (dog = reg.exec(str)) {
dogs.push(dog);
}
console.log(dogs); // array[4]
ES2020 字符串引入了 matchAll 方法,可以一次性取出所有匹配,并返回一個結果的迭代器:
const reg = /dog/g;
const str = 'dog1dog2dog3dog4';
const dogs = [];
// 一次性取出所有匹配結果,并返回迭代器
for (const dog of str.matchAll(reg)) {
dogs.push(dog);
}
console.log(dogs); // array[4]
#03 BigInt 類型
在 JavaScript 中,數值類型 Number 被保存為** 64 位浮點數,所以計算精度和表示范圍都有一定限制。ES2020 新增了 BigInt 數據類型,這也是 JavaScript 引入的第八種基本類型**。
BigInt 被用來表示整數,以 n 為后綴,且沒有位數限制:
// BigInt 使用 n 作為整數后綴
const a = 416576138978n; // 后綴使用 n
const b = 861387698979n;
console.log(a * b); // 358833561803815532703462n
// 普通整數精度丟失
console.log(Number(a) * Number(b)); // 3.5883356180381556e+23
BitInt 整數和普通整數不全等(類型不同):
console.log(123n === 123); // false
自然還有配套的 BigInt 對象,可以通過對應的構造函數創(chuàng)建 BigInt 數值,
const num = BigInt('1233243423');
BigInt 對象具備 asUintN、parseInt 等方法:
// 1233243423n。asUintN 將給定的 BigInt 轉為 0 到 2^width - 1 之間對應的值。
BigInt.asUintN(1024, num);
// 23423423n。類似于 Number.parseInt(),將一個字符串轉換成指定進制的 BigInt。
//(現有瀏覽器不一定支持)
BigInt.parseInt('23423423', 10);
#04 Promise 擴展
4.1 Promise.allSettled()
const promises = [
fetch('/api-1'),
fetch('/api-2'),
fetch('/api-3'),
];
Promise.allSettled(promises)
.then(results => console.log(results));
allSettled() 方法將一組 Promise 實例參數封裝成一個 Promise 實例,只有當被封裝的所有 Promise 實例全部返回結果后(不管是成功 fulfilled 還是失敗 rejected)才算結束。
同時所有 Promise 實例的返回結果被封裝在 results 參數中傳遞給通過 .then() 函數指定的回調函數。
results 中的每個元素都對應一個 Promise 實例的執(zhí)行結果,結果中的 status 表示執(zhí)行成功與否。如果狀態(tài)為 fulfilled,則另一個字段為 value 表示操作返回值。如果狀態(tài)為 rejected,則另一個字段為 reason 表示錯誤信息:
Promise.allSettled(promises)
.then(results => console.log(results));
// results
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
#05 globalThis
在 ES2020 之前,瀏覽器、Web Worker、Node.js 等不同環(huán)境的「頂層對象」有所不同。例如瀏覽器中可以通過 window 或 self 來獲取頂層對象,Web Worker 通過 self 獲取頂層對象,到了 Node.js 則是 global。
這種不一致性會導致代碼需要進行無謂的判斷,添加復雜性。所以 ES2020 引入了統一的 globalThis 表示頂層對象。以后不管在哪個環(huán)境,都可以通過 globalThis 拿到頂層對象。
#06 鏈判斷運算符
在平時的編程中,我們經常需要獲取深層次屬性,例如 system.user.addr.province.name。但在獲取 name 這個屬性前我們需要一步步的判斷前面的屬性是否存在,否則并會報錯:
const name = (system && system.user && system.user.addr && system.user.addr.province && system.user.addr.province.name) || 'default';
為了簡化上述過程,ES2020 引入了「鏈判斷運算符」?.:
const name = system?.user?.addr?.province?.name || 'default';
?. 會判斷運算符左側的對象是否存在,如果不存在即為 null 或 undefined 那么就會不再繼續(xù)而是提前返回 undefined,存在則繼續(xù)往下運算。
「鏈判斷運算符」還有另外幾種形式:
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b()
a?.()
// 等同于
a == null ? undefined : a()
#06 Null 判斷運算符
在編程過程我們經常會遇到這樣的常見,如果某個屬性不為 null 和 undefined,那么就獲取該屬性,如果該屬性為 null 或 undefined,則取一個默認值:
const name = dogName ? dogName : 'default';
可以通過 || 來簡化:
const name = dogName || 'default';
但是 || 的寫法存在一定的缺陷,當 dogName 為 0 或 false 的時候也會走到 default 的邏輯。
所以 ES2020 引入了 ?? 運算符。只有 ?? 左邊為 null 或 undefined時才返回右邊的值:
const dogName = false;
const name = dogName ?? 'default'; // name = false;
#07 for-in 循環(huán)的規(guī)則
ES2020 對 for-in 循環(huán)進行了遍歷規(guī)則的補充,詳情可參閱proposal-for-in-order
參考資料
tc39 From GitHub
MDN Web Docs
ECMAScript 6 教程
Advanced ES6 Destructuring Techniques
The final feature set of ECMAScript 2016 (ES7)
ECMAScript 2017 (ES8): the final feature set
ECMAScript 2018: the final feature set
ES2018: asynchronous iteration
ECMAScript 2019: the final feature set
ECMAScript 2020: the final feature set
汪
汪