ES6~ES11 特性介紹之 ES7~ES11 篇

原本想稍微整理一下 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 多線程之間通過 postMessageonmessage 等方式進行通信傳遞消息。但當數據量比較大時,直接傳遞數據效率較低。因此 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()函數指向第二個元素.... 重復并可迭代數據結構中所有元素。
  • IteratorResultIterator 對象 每一次調用 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()

ES2018Promise 方面也做了一些補充,添加了 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 添加了 trimStarttrimEnd 這兩個新方法,分別用來去除字符串的首部和尾部空白字符

const hiMessage = `   Hello Word!   `;

console.log(hiMessage.trimStart());  // 'Hello Word!   '
console.log(hiMessage.trimEnd());  // '   Hello Word!'

#02 數組擴展

ES2019 在數組類型上添加了 flat()flatMap() 這兩個新方法。

  1. 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);  
  1. 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) 所接受的參數是像 ArrayMap 那樣實現了 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() 方法進行了擴展,以前這個方法只會輸出函數代碼,但會省略注釋和空格。ES2019toString() 則會保留注釋、空格等,即輸出的是原汁原味的原始代碼

function sayHi() {
  /* dog dog */
  console.log('wangwang.');
}

sayHi.toString();  // 將輸出和上面一樣的原始代碼

#06 JSON.stringify() 的優化

JSON 數據需要為是 UTF-8 編碼,但在 ES2019 之前 JSON.stringify() 可能輸出不符合 UTF-8 規范的字符。

原因在于 UTF-8 標準中為了表示 oxFFFF 的字符,將 0xD8000xDFFF 范圍內的編碼用來組合使用,不能單獨使用。此時將這個范圍內的編碼傳給 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 導入導出混合寫法 一節中介紹了 importexport 混合用法,例如:

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 對象具備 asUintNparseInt 等方法:

// 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 WorkerNode.js 等不同環境的「頂層對象」有所不同。例如瀏覽器中可以通過 windowself 來獲取頂層對象,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';

?. 會判斷運算符左側的對象是否存在,如果不存在即為 nullundefined 那么就會不再繼續而是提前返回 undefined,存在則繼續往下運算。

「鏈判斷運算符」還有另外幾種形式:

a?.[x]
// 等同于
a == null ? undefined : a[x]

a?.b()
// 等同于
a == null ? undefined : a.b()

a?.()
// 等同于
a == null ? undefined : a()

#06 Null 判斷運算符

在編程過程我們經常會遇到這樣的常見,如果某個屬性不為 nullundefined,那么就獲取該屬性,如果該屬性為 nullundefined,則取一個默認值:

const name = dogName ? dogName : 'default';

可以通過 || 來簡化:

const name =  dogName || 'default';

但是 || 的寫法存在一定的缺陷,當 dogName0false 的時候也會走到 default 的邏輯。

所以 ES2020 引入了 ?? 運算符。只有 ?? 左邊為 nullundefined時才返回右邊的值:

const dogName = false;
const name =  dogName ?? 'default';  // name = false;

#07 for-in 循環的規則

ES2020for-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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容