ES2020(即 ES11)上週(2020 年 6 月)已經正式發佈,在此以前進入 Stage 4 的 10 項提案均已歸入規範,成爲 JavaScript 語言的新特性html
ES Module 迎來了一些加強:前端
正式支持了安全的鏈式操做:git
?.
可以在屬性訪問、方法調用前檢查其是否存在??
提供了大數運算的原生支持:es6
一些基礎 API 也有了新的變化:github
all
、race
同樣具備短路特性index
、groups
等)this
的通用方法for-in
循環的某些行爲咱們知道ES Module是一套靜態的模塊系統:正則表達式
The existing syntactic forms for importing modules are static declarations.
靜態體如今:數組
They accept a string literal as the module specifier, and introduce bindings into the local scope via a pre-runtime "linking" process.
import/export
聲明只能出如今頂層做用域,不支持按需加載、懶加載 例如:promise
if (Math.random()) { import 'foo'; // SyntaxError } // You can’t even nest `import` and `export` // inside a simple block: { import 'foo'; // SyntaxError }
這種嚴格的靜態模塊機制讓基於源碼的靜態分析、編譯優化有了更大的發揮空間:瀏覽器
This is a great design for the 90% case, and supports important use cases such as static analysis, bundling tools, and tree shaking.
但對另外一些場景很不友好,好比:安全
import
聲明引用的全部模塊(包括初始化暫時用不到的模塊)都會在初始化階段前置加載,影響首屏性能module-en
、module-zh
等)爲了知足這些須要動態加載模塊的場景,ES2020 推出了動態 import 特性(import()
):
import(specifier)
import()
「函數」輸入模塊標識specifier
(其解析規則與import
聲明相同),輸出Promise
,例如:
// 目標模塊 ./lib/my-math.js function times(a, b) { return a * b; } export function square(x) { return times(x, x); } export const LIGHTSPEED = 299792458; // 當前模塊 index.js const dir = './lib/'; const moduleSpecifier = dir + 'my-math.mjs'; async function loadConstant() { const myMath = await import(moduleSpecifier); const result = myMath.LIGHTSPEED; assert.equal(result, 299792458); return result; } // 或者不用 async & await function loadConstant() { return import(moduleSpecifier) .then(myMath => { const result = myMath.LIGHTSPEED; assert.equal(result, 299792458); return result; }); }
與import
聲明相比,import()
特色以下:
module
,在普通的script
中也能使用注意,雖然長的像函數,但import()
其實是個操做符,由於操做符可以攜帶當前模塊相關信息(用來解析模塊表示),而函數不能:
Even though it works much like a function,
import()
is an operator: in order to resolve module specifiers relatively to the current module, it needs to know from which module it is invoked. A normal function cannot receive this information as implicitly as an operator can. It would need, for example, a parameter.
另外一個 ES Module 新特性是import.meta
,用來透出模塊特定的元信息:
import.meta, a host-populated object available in Modules that may contain contextual information about the Module.
好比:
__dirname
、__filename
script
標籤:例如瀏覽器支持的document.currentScript
process.mainModule
諸如此類的元信息均可以掛到import.meta
屬性上,例如:
// 模塊的 URL(瀏覽器環境) import.meta.url // 當前模塊所處的 script 標籤 import.meta.scriptElement
但須要注意的是,規範並無明肯定義具體的屬性名和含義,都由具體實現來定,因此特性提案裏的但願瀏覽器支持的這兩個屬性未來可能支持也可能不支持
P.S.import.meta
自己是個對象,原型爲null
第三個 ES Module 相關的新特性是另外一種模塊導出語法:
export * as ns from "mod";
同屬於export ... from ...
形式的聚合導出,做用上相似於:
import * as ns from "mod"; export {ns};
但不會在當前模塊做用域引入目標模塊的各個 API 變量
P.S.對照import * as ns from "mod";
語法,看起來像是 ES6 模塊設計中排列組合的一個疏漏;)
至關實用的一個特性,用來替代諸如此類冗長的安全鏈式操做:
const street = user && user.address && user.address.street;
可換用新特性(?.
):
const street = user?.address?.street;
語法格式以下:
obj?.prop // 訪問可選的靜態屬性 // 等價於 (obj !== undefined && obj !== null) ? obj.prop : undefined obj?.[«expr»] // 訪問可選的動態屬性 // 等價於 (obj !== undefined && obj !== null) ? obj[«expr»] : undefined func?.(«arg0», «arg1») // 調用可選的函數或方法 // 等價於 (func !== undefined && func !== null) ? func(arg0, arg1) : undefined
P.S.注意操做符是?.
而不是單?
,在函數調用中有些奇怪alert?.()
,這是爲了與三目運算符中的?
區分開
機制很是簡單,若是出如今問號前的值不是undefined
或null
,才執行問號後的操做,不然返回undefined
一樣具備短路特性:
// 在 .b?.m 時短路返回了 undefined,而不會 alert 'here' ({a: 1})?.a?.b?.m?.(alert('here'))
與&&
相比,新的?.
操做符更適合安全進行鏈式操做的場景,由於:
?.
遇到屬性/方法不存在就返回undefined
,而不像&&
同樣返回左側的值(幾乎沒什麼用)?.
只針對null
和undefined
,而&&
遇到任意假值都會返回,有時沒法知足須要例如經常使用的正則提取目標串,語法描述至關簡潔:
'string'.match(/(sing)/)?.[1] // undefined // 以前須要這樣作 ('string'.match(/(sing)/) || [])[1] // undefined
還能夠配合 Nullish coalescing Operator 特性填充默認值:
'string'.match(/(sing)/)?.[1] ?? '' // '' // 以前須要這樣作 ('string'.match(/(sing)/) || [])[1] || '' // '' // 或者 ('string'.match(/(sing)/) || [, ''])[1] // ''
一樣引入了一種新的語法結構(??
):
actualValue ?? defaultValue // 等價於 actualValue !== undefined && actualValue !== null ? actualValue : defaultValue
用來提供默認值,當左側的actualValue
爲undefined
或null
時,返回右側的defaultValue
,不然返回左側actualValue
相似於||
,主要區別在於??
只針對null
和undefined
,而||
遇到任一假值都會返回右側的默認值
新增了一種基礎類型,叫BigInt
,提供大整數運算支持:
BigInt is a new primitive that provides a way to represent whole numbers larger than 2^53, which is the largest number Javascript can reliably represent with the Number primitive.
JavaScript 中Number
類型所能準確表示的最大整數是2^53
,不支持對更大的數進行運算:
const x = Number.MAX_SAFE_INTEGER; // 9007199254740991 即 2^53 - 1 const y = x + 1; // 9007199254740992 正確 const z = x + 2 // 9007199254740992 錯了,沒變
P.S.至於爲何是 2 的 53 次方,是由於 JS 中數值都以 64 位浮點數形式存放,刨去 1 個符號位,11 個指數位(科學計數法中的指數),剩餘的 52 位用來存放數值,2 的 53 次方對應的這 52 位所有爲 0,能表示的下一個數是2^53 + 2
,中間的2^53 + 1
沒法表示:
[caption id="attachment_2213" align="alignnone" width="625"]<img src="http://www.ayqy.net/cms/wordpress/wp-content/uploads/2020/06/JavaScript-Max-Safe-Integer-1024x518.jpg" alt="JavaScript Max Safe Integer" width="625" height="316" class="size-large wp-image-2213" /> JavaScript Max Safe Integer[/caption]
具體解釋見BigInts in JavaScript: A case study in TC39
BigInt
類型的出現正是爲了解決此類問題:
9007199254740991n + 2n // 9007199254740993n 正確
引入的新東西包括:
n
表示大整數,例如9007199254740993n
、0xFFn
(二進制、八進制、十進制、十六進制字面量統統能夠後綴個n
變成BigInt
)bigint
基礎類型:typeof 1n === 'bigint'
BigInt
例如:
// 建立一個 BigInt 9007199254740993n // 或者 BigInt(9007199254740993) // 乘法運算 9007199254740993n * 2n // 冪運算 9007199254740993n ** 2n // 比較運算 0n === 0 // false 0n === 0n // true // toString 123n.toString() === '123'
P.S.關於 BigInt API 細節的更多信息,見ECMAScript feature: BigInt – arbitrary precision integers
須要注意的是BigInt
不能與Number
混用進行運算:
9007199254740993n * 2 // 報錯 Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
而且BigInt
只能表示整數,因此除法直接取整(至關於Math.trunc()
):
3n / 2n === 1n
基礎 API 也有一些新的變化,包括 Promise、字符串正則匹配、for-in
循環等
繼Promise.all、Promise.race以後,Promise
新增了一個靜態方法叫allSettled
:
// 傳入的全部 promise 都有結果(從 pending 狀態變成 fulfilled 或 rejected)以後,觸發 onFulfilled Promise.allSettled([promise1, promise2]).then(onFulfilled);
P.S.另外,any
也在路上了,目前(2020/6/21)處於 Stage 3
相似於all
,但不會由於某些項rejected
而短路,也就是說,allSettled
會等到全部項都有結果(不管成功失敗)後才進入Promise
鏈的下一環(因此它必定會變成 Fulfilled 狀態):
A common use case for this combinator is wanting to take an action after multiple requests have completed, regardless of their success or failure.
例如:
Promise.allSettled([Promise.reject('No way'), Promise.resolve('Here')]) .then(results => { console.log(results); // [ // {status: "rejected", reason: "No way"}, // {status: "fulfilled", value: "Here"} // ] }, error => { // No error can get here! })
字符串處理的一個常見場景是想要匹配出字符串中的全部目標子串,例如:
const str = 'es2015/es6 es2016/es7 es2020/es11'; str.match(/(es\d+)\/es(\d+)/g) // 順利獲得 ["es2015/es6", "es2016/es7", "es2020/es11"]
match()
方法中,正則表達式所匹配到的多個結果會被打包成數組返回,但沒法得知每一個匹配除結果以外的相關信息,好比捕獲到的子串,匹配到的index
位置等:
This is a bit of a messy way to obtain the desired information on all matches.
此時只能求助於最強大的exec
:
const str = 'es2015/es6 es2016/es7 es2020/es11'; const reg = /(es\d+)\/es(\d+)/g; let matched; let formatted = []; while (matched = reg.exec(str)) { formatted.push(`${matched[1]} alias v${matched[2]}`); } console.log(formatted); // 獲得 ["es2015 alias v6", "es2016 alias v7", "es2020 alias v11"]
而 ES2020 新增的matchAll()
方法就是針對此類種場景的補充:
const results = 'es2015/es6 es2016/es7 es2020/es11'.matchAll(/(es\d+)\/es(\d+)/g); // 轉數組處理 Array.from(results).map(r => `${r[1]} alias v${r[2]}`); // 或者從迭代器中取出直接處理 // for (const matched of results) {} // 獲得結果同上
注意,matchAll()
不像match()
同樣返回數組,而是返回一個迭代器,對大數據量的場景更友好
JavaScript 中經過for-in
遍歷對象時 key 的順序是不肯定的,由於規範沒有明肯定義,而且可以遍歷原型屬性讓for-in
的實現機制變得至關複雜,不一樣 JavaScript 引擎有各自根深蒂固的不一樣實現,很難統一
因此 ES2020 不要求統一屬性遍歷順序,而是對遍歷過程當中的一些特殊 Case 明肯定義了一些規則:
具體見13.7.5.15 EnumerateObjectProperties
最後一個新特性是globalThis
,用來解決瀏覽器,Node.js 等不一樣環境下,全局對象名稱不統一,獲取全局對象比較麻煩的問題:
var getGlobal = function () { // the only reliable means to get the global object is // `Function('return this')()` // However, this causes CSP violations in Chrome apps. 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
值:
The global variable globalThis is the new standard way of accessing the global object. It got its name from the fact that it has the same value as this in global scope.
P.S.爲何不叫global
?是由於global
可能會影響現有的一些代碼,因此另起一個globalThis
避免衝突
至此,ES2020 的全部新特性都清楚了
比起ES2019,ES2020 算是一波大更新了,動態 import、安全的鏈式操做、大整數支持……全都加入了豪華午飯
關注「前端向後」微信公衆號,你將收穫一系列「用心原創」的高質量技術文章,主題包括但不限於前端、Node.js以及服務端技術
本文首發於 ayqy.net ,原文連接:http://www.ayqy.net/blog/es2020/