原本想稍微整理一下 ES 新特性,沒想到花了相當多的時間,本文也巨長,依然推薦使用 簡悅 生成目錄。
原文竟然由于過長無法發布,第一次知道簡書還有文章字數限制。現在只能拆成兩篇發布。
本系列文章
ES6~ES11 特性介紹之 ES6 篇
ES6~ES11 特性介紹之 ES7~ES11 篇
特性列表
ES7(ES 2016) 特性
2016 年 6 月推出的 ES2016
規范也就是 ES7 只做了微量的更新,添加了兩個特性:
- Array.prototype.includes
- 指數操作符
#01 Array.prototype.includes()
在上文 ES6 部分的 6.3.3 includes(), startsWith(), endsWith(), repeat()
一節中,我們介紹了字符串的 includes
方法來判斷是否包含指定的「參數字符串」。ES2016
則繼續補充了數組的 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 函數
一節介紹了 Generator 函數,其中 8.5 在異步編程上的應用
小節則是介紹了 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);
// 主線程將共享內存的地址發送出去
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 對象
并是提供「原子性操作」的角色。
值得注意的是提供了「原子性操作」并不意味著不再存在并發的資源競爭問題,有興趣的可以查閱LevelDB 中的跳表實現 中的 「并發處理」部分。
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
規定函數的參數列表的結尾可以為逗號
:
function Dog(
name,
age,
sex, // ES2017 之前非法,ES2017 后合法
) { /* 函數體 */ }
和對象最后屬性結尾可以為逗號
的理由一樣,如果沒有該逗號,添加一個參數就需要修改兩行(一行需要加個逗號,一行加屬性),這在 git
多人協作時會干擾對本次修改的查看。
ES9(ES 2018) 特性
#01 異步迭代器
在上文 ES6 部分的 #11 Iterator 迭代器
一節中,我們已經介紹了 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
語句進行循環,而對于異步迭代器則可以使用 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
實例最終成功或失敗都會執行的方法:
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
實例的執行結果,所以通常在 finally()
方法內執行的是與 promise
狀態無關的操作。
#03 對象的 Rest/Spread
在 ES6 部分的 2.1.3 rest 操作符 ...
節 和 6.5.1 spread 擴展運算符與數組
節分別簡單介紹了 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 屬性
的指定。例如指定查找范圍為「希臘符號」,以前只能限定字符區間,現在可以直接指定屬性:
// \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 模板字符串
一節中介紹了模板字符串和標簽模板,其中標簽模板默認會將字符串轉義:
// 報錯。\unicode 轉義失敗
const doc = latex`\newcommand{\unicode}{\textbf{Unicode!}}`;
上述代碼中的 \unicode
被當作 Unicode
字符進行轉義,從而導致錯誤。ES2018
對此的更新是「放寬」轉義限制。遇到無法轉義的字符時返回 undefined
而不是報錯,這樣可以使得程序繼續運行。同時函數可以從 6.3.1 模板字符串
一節中提及的第一個參數中的 raw
屬性拿到原始字符串。
但注意上述規則針對標簽模板,普通模板字符串遇到無法轉義的 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 格式規范允許字符串里直接包含 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 節介紹了 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() 的優化
JSON 數據需要為是 UTF-8
編碼,但在 ES2019
之前 JSON.stringify()
可能輸出不符合 UTF-8
規范的字符。
原因在于 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() 穩定
ES2019
之前數組的 sort()
方法是否穩定是不明確的,任由瀏覽器自己實現。ES2019
開始明確規定排序算法必須是穩定的。
ES11(ES 2020) 特性
#01 模塊化擴展
1.1 動態導入 import()
在上文 ES6 部分的 04 模塊 Module
節中介紹了模塊相關的知識,其中模塊導入使用 import
命令實現,但是了解到 import
命令是一種靜態導入,所以在 ES2020
之前我們無法像 CommonJS
中的 require
那樣動態導入模塊。那么「條件導入」、「運行時確定模塊」等功能就會被限制。
ES2020
引入了 import()
操作符來實現動態導入:
const module = 'animal';
if (type === 1) {
module = 'dog';
} else if (type === 2) {
module = 'cat';
}
// 導入模塊的名稱可以動態計算,即運行時再確定
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 導入導出混合寫法
一節中介紹了 import
和 export
混合用法,例如:
export * from 'my_module';
但是還有一種導入寫法沒有對應的混合寫法:
import * as module from "xxx";
ES2020
補充了上述語句對應的混合導出寫法:
export * as module from "xxx";
02 字符串擴展
2.1 matchAll
在正則表達式中,如果一個字符串有多個匹配,我們可以通過循環來依次獲取:
const reg = /dog/g;
const str = 'dog1dog2dog3dog4';
const dogs = [];
let dog;
// 循環調用 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
對象,可以通過對應的構造函數創建 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
實例的執行結果,結果中的 status
表示執行成功與否。如果狀態為 fulfilled
,則另一個字段為 value
表示操作返回值。如果狀態為 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
等不同環境的「頂層對象」有所不同。例如瀏覽器中可以通過 window
或 self
來獲取頂層對象,Web Worker
通過 self
獲取頂層對象,到了 Node.js
則是 global
。
這種不一致性會導致代碼需要進行無謂的判斷,添加復雜性。所以 ES2020
引入了統一的 globalThis
表示頂層對象。以后不管在哪個環境,都可以通過 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
那么就會不再繼續而是提前返回 undefined
,存在則繼續往下運算。
「鏈判斷運算符」還有另外幾種形式:
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 循環的規則
ES2020
對 for-in
循環進行了遍歷規則的補充,詳情可參閱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
汪
汪