ECMAScript edition 11
ECMAScript 2020 是 ECMAScript 第 11 版規范,英文比較好的可以看看 ECMA-262 闡述的標準,在這里記錄一下學習筆記,并且盡量舉一些例子,便于理解,
1. dynamic import
ES Module 是一套靜態的模塊系統,不支持按需加載/懶加載,不支持動態計算的模塊名,比如我想在選擇分支中引入模塊:
if (condition) {
const foo = import 'foo'; // 報錯,SyntaxError
}
不得不使用 require() 代替:
if (condition) {
const foo = require('foo');
}
import 這種設計讓基于原始碼的 靜態分析 和 Tree Shaking 有了很大發揮空間,但是對于某些場景不夠友好:
- 當靜態匯入的模塊很明顯的降低了代碼的加載速度且被使用的可能性很低,或者并不需要馬上使用它,
- 當靜態匯入的模塊很明顯的占用了大量系統記憶體且被使用的可能性很低
- 當被匯入的模塊,在加載時并不存在,需要異步獲取
- 當匯入模塊的說明符,需要動態構建,(靜態匯入只能使用靜態說明符)
- 當被匯入的模塊有副作用(這里說的副作用,可以理解為模塊中會直接運行的代碼),這些副作用只有在觸發了某些條件才被需要時,(原則上來說,模塊不能有副作用,但是很多時候,你無法控制你所依賴的模塊的內容)
為了滿足像上面列舉的這些個場景,ES2020 推出了 dynamic import 特性(import()):
import(specifier);
關鍵字 import 可以像呼叫函式一樣來動態的匯入模塊,以這種方式呼叫,將回傳一個 promise 物件,
loadMod = asnyc () => {
const myMod = await import(moduleSpecifier);
const res = myMod.foo();
return res;
}
上面使用 asnyc&await 接收動態匯入的異步結果,
?? 注意,雖然這玩意兒長得像一個函式,但是實際上是一個運算子,因為運算子能夠帶上當前模塊資訊,而函式不能,
盡管動態引入提供了很大的方便,但是不能夠在專案中進行濫用,如同開始所說的,靜態框架能提供良好的 靜態分析 和 Tree Shaking,
2. import.meta
顧名思義,這個特性是用來帶出模塊特定的元資訊的,比如:
- 模塊的 URL 或者檔案名
- 所處的 script 標簽
- 入口模塊
但是在標準里被沒有明確規定需要透露的屬性和含義,全都有具體實作來定制,也就是說,將來這個屬性里能獲取到什么屬性,都是由廠商說了算,
3. export * as ns from 'module'
// menu.js
export * as ns from './info';
相當于以下代碼:
import * as ns from './info';
export { ns };
?? 不過在 menu.js 中是獲取不到 ns 的,因為這個語法不會真的將模塊匯入,
4. 空值合并運算子
控制合并運算子看起來和 && 以及 || 這樣的邏輯運算子很像,實際上是用來提供默認值的,也算是一種簡略的邏輯判斷:
actualVal ?? defaultVal;
// 等價于
actualVal !== undefined && actualVal !== null ? actualVal : defaultVal;
也許之前你會用這樣的判斷:
const foo = actualVal || defaultVal;
但是這兩者中有一個顯著的不同,因為 JavaScript 是一門動態語言,當使用 || 時會對前面的變數進行隱式轉換:
let foo;
const actualVal = 0; // fasly 值
foo = actualVal || 1;
console.log(foo); // 1
foo = actualVal ?? 1;
console.log(foo); // 0
前一種方法,當我們希望真實的值為 0(或者除 null 和 undefined 以外的 falsy 值) 的時候,卻被錯誤地分配為 1,而 ?? 運算子只有在 actualVal 為 null 或者 undefined 的時候才會回傳右側運算元,這更符合在實際業務中的邏輯處理,例如等級、金錢、數量等單位制屬性,
5. 可選鏈運算子
可選鏈 ?. 是一種訪問嵌套物件屬性的安全的方式,即使中間的屬性不存在,也不會出現錯誤,
眾所周知,在 JavaScript 里做鏈式取值操作很容易導致程式報錯,例如:
// 獲取用戶的地址
const address = response.userInfo.address.street;
如果 response 中不存在 userInfo 這個屬性,那么再去嘗試取 address 的值就會報錯,之后的 street 也需要檢查前面的屬性是否存在,ES2020 之前都是像這樣避免這個錯誤:
// 用 && 運算子
const address = response.userInfo && response.userInfo.address && response.userInfo.address.street;
// 三元運算式
const street = response.userInfo ? (response.userInfo.address ? response.userInfo.address.street : null) : null;
這樣的缺點也非常明顯,那就是重復撰寫前面的變數,當我們想要獲取更深層的屬性時,代碼就會變得又臭又長,難以閱讀,這就是為什么我們需要可選鏈 .? 來徹底地解決以上的問題,換用新特性賦值:
const address = response.userInfo?.address?.street;
如果可選鏈 ?. 前面的部分是 undefined 或者 null,它會停止運算并回傳該部分,(短路效應)
另外還有 ?.() 以及 ?.[] 這樣的變體方法,原理是差不多的,
總結,可選鏈 ?. 語法有三種形式:
-
obj?.prop—— 如果obj存在則回傳obj.prop,否則回傳undefined -
obj?.[prop]—— 如果obj存在則回傳obj[prop],否則回傳undefined -
obj.method?.()—— 如果obj.method存在則呼叫obj.method(),否則回傳undefined
?? 應該慎重使用可選鏈,僅在當左邊部分不存在也沒問題的情況下使用為宜,以保證在代碼中有編程上的錯誤出現時,也不會對我們隱藏,
6. BigInt
BigInt 是一種數字型別的資料,它可以表示任意精度格式的整數,在此之前,JS 中安全的最大數字是 9009199254740991,即 2^53-1,在控制臺中輸入 Number.MAX_SAFE_INTEGER 即可查看,超過這個值,JS 沒有辦法精確表示,另外,大于或等于 2 的 1024 次方的數值,JS 無法表示,會回傳 Infinity,
BigInt 即解決了這兩個問題,BigInt 只用來表示整數,沒有位數的限制,任何位數的整數都可以精確表示,為了和 Number 型別進行區分,BigInt 型別的資料必須添加后綴 n,
//Number型別在超過9009199254740991后,計算結果即出現問題
const num1 = 90091992547409910;
console.log(num1 + 1); //90091992547409900
//BigInt 計算結果正確
const num2 = 90091992547409910n;
console.log(num2 + 1n); //90091992547409911n
還可以使用 BigInt 物件來初始化 BigInt 實體:
console.log(BigInt(999)); // 注意:沒有 new 關鍵字
7. matchAll
字串處理的一個常見場景是想要匹配字串中所有的目標子串,例如:
const str = 'es2015/es6 es2016/es7 es2020/es11';
str.match(/(es\d+)\/es(\d+)/g); // ["es2015/es6", "es2016/es7" "es2020/es11"]
但只能獲取到字串,并不能獲取到額外資訊例如捕獲到的字串、索引值等,這時候只能使用 exec 方法,
而新增的 matchAll 方法就是用于處理這種場景的:
const reg = /[0-3]/g;
const data = 'https://www.cnblogs.com/jsmax/archive/2020/12/14/2020';
console.log(data.matchAll(reg)); // data.matchAll 的回傳值是一個迭代器
console.log([...data.matchAll(reg)]);
/**
* 0: ["2", index: 0, input: "2020", groups: undefined]
* 1: ["0", index: 1, input: "2020", groups: undefined]
* 2: ["2", index: 2, input: "2020", groups: undefined]
* 3: ["0", index: 3, input: "2020", groups: undefined]
*/
回傳迭代器的做法對資料量大的場景很友好,??
8. Promise.allSettled
promise.allSettled 和 promise.all 很像,區別在于它不會因為某個 rejected 狀態進入失敗狀態,而是等所有任務都得到結果后才進入 Promise 鏈的下一環,也就是說它無論如何都會進入 fulfilled 狀態,
const promise1 = Promise.resolve(100);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'info'));
const promise3 = new Promise((resolve, reject) => setTimeout(resolve, 200, 'name'));
Promise.allSettled([promise1, promise2, promise3]).then((results) => console.log(result));
/*
[
{ status: 'fulfilled', value: 100 },
{ status: 'rejected', reason: 'info' },
{ status: 'fulfilled', value: 'name' }
]
*/
可以看到,它回傳的結果是一個陣列,里面包含了對應的 promise 的狀態 status 屬性,如果是 fulfilled 狀態則帶出 value 值,否則用 reason 屬性帶出失敗原因,
9. globalThis
globalThis 是用來解決不同環境下的全域物件不統一,獲取全域物件比較麻煩的問題,在 ES11 標準之前,可能需要用這樣一個函式來獲取全域物件:
const globalThis = (function () {
if (typeof self !== 'undefined') {
return self;
}
if (typeof window !== 'undefined') {
return window;
}
if (typeof global !== 'undefined') {
return global;
}
throw new Error('unable to locate global object');
})();
globalThis 作為統一的全域物件獲取方式,總是指向全域作用域中的 this 值,
10. for-in 回圈的規范
JavaScript 中通過 for-in 遍歷物件時 key 的順序是不確定的,因為沒有明確定義,所以不同的引擎有各自不同的實作,很難統一,所以 ES2020 不要求統一遍歷順序,而是對遍歷程序中一些特殊的案例明確定義了一些規則:
- 無法遍歷到 Symbol 型別的屬性
- 遍歷程序中,目標物件的屬性能夠被洗掉,未遍歷到卻被洗掉了的屬性會被忽略
- 遍歷程序中,如果有新增屬性,不保證新增的屬性能在本次遍歷中處理到
- 屬性名不會重復出現
- 目標物件整條原型鏈上的屬性都能遍歷到
總結
相比于 ES2019,ES2020 的更新算是非常豪華了,既帶來了日常作業中常用的 API,也有工程構建和基礎運算的增強,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/234759.html
標籤:其他
下一篇:第三章 陳述句和運算式
