天天看點

【ES】574- 種草 ES2020 新特性

【ES】574- 種草 ES2020 新特性

作者:李大雷

這幾年,Ecma TC39 一年一次更新 ECMAScript 規範标準,截止目前,以下特性已進入 finished 狀态。現在帶大家體驗種草 ES2020 新特性。

一:Promise.allSettled

Promise.all 缺陷

都知道 ​

​Promise.all​

​​ 具有并發執行異步任務的能力。但它的最大問題就是如果其中某個任務出現異常(​

​reject​

​​),所有任務都會挂掉,​

​Promise​

​​ 直接進入 ​

​reject​

​ 狀态。

想象這個場景:你的頁面有三個區域,分别對應三個獨立的接口資料,使用 ​

​Promise.all​

​​ 來并發三個接口,如果其中任意一個接口服務異常,狀态是 reject,這會導緻頁面中該三個區域資料全都無法渲染出來,因為任何 ​

​reject​

​ 都會進入 catch 回調, 很明顯,這是無法接受的,如下:

Promise.all([
Promise.reject({code: 500, msg: '服務異常'}),
Promise.resolve({ code: 200, list: []}),
Promise.resolve({code: 200, list: []})
])
.then((ret) => {
// 如果其中一個任務是 reject,則不會執行到這個回調。
    RenderContent(ret);
})
.catch((error) => {
// 本例中會執行到這個回調
// error: {code: 500, msg: "服務異常"}
})      

Promise.allSettled 的優勢

我們需要一種機制,如果并發任務中,無論一個任務正常或者異常,都會傳回對應的的狀态(​

​fulfilled​

​​ 或者 ​

​rejected​

​​)與結果(業務 ​

​value​

​​ 或者 拒因 ​

​reason​

​​),在 ​

​then​

​​ 裡面通過 ​

​filter​

​​ 來過濾出想要的業務邏輯結果,這就能最大限度的保障業務目前狀态的可通路性,而 ​

​Promise.allSettled​

​ 就是解決這問題的。

Promise.allSettled([
Promise.reject({code: 500, msg: '服務異常'}),
Promise.resolve({ code: 200, list: []}),
Promise.resolve({code: 200, list: []})
])
.then((ret) => {
/*
        0: {status: "rejected", reason: {...}}
        1: {status: "fulfilled", value: {...}}
        2: {status: "fulfilled", value: {...}}
    */
// 過濾掉 rejected 狀态,盡可能多的保證頁面區域資料渲染
    RenderContent(ret.filter((el) => {
return el.status !== 'rejected';
    }));
});      

二:可選鍊

​可選鍊​

​ 可讓我們在查詢具有多層級的對象時,不再需要進行備援的各種前置校驗。

日常開發中,我們經常會遇到這種查詢

var name = user && user.info && user.info.name;      

又或是這種

var age = user && user.info && user.info.getAge && user.info.getAge();      

這是一種醜陋但又不得不做的前置校驗,否則很容易命中 ​

​Uncaught TypeError: Cannot read property...​

​ 這種錯誤,這極有可能讓你整個應用挂掉。

用了 Optional Chaining ,上面代碼會變成

var name = user?.info?.name;      
var age = user?.info?.getAge?.();      

可選鍊中的 ​

​?​

​ 表示如果問号左邊表達式有值, 就會繼續查詢問号後面的字段。根據上面可以看出,用可選鍊可以大量簡化類似繁瑣的前置校驗操作,而且更安全。

三:空值合并運算符

當我們查詢某個屬性時,經常會遇到,如果沒有該屬性就會設定一個預設的值。比如下面代碼中查詢玩家等級。

var level = (user.data && user.data.level) || '暫無等級';      

在 JS 中,空字元串、0 等,當進行邏輯操作符判斷時,會自動轉化為 false。在上面的代碼裡,如果玩家等級本身就是 0 級, 變量 level 就會被指派 ​

​暫無等級​

​ 字元串,這是邏輯錯誤。

var level;
if (typeof user.level === 'number') {
    level = user.level;
} else if (!user.level) {
    level = '暫無等級';
} else {
    level = user.level;
}      

來看看用空值合并運算符如何處理

// {
//   "level": 0
// }
var level = `${user.level}級` ?? '暫無等級';
// level -> '0級'      

用空值合并運算在邏輯正确的前提下,代碼更加簡潔。

空值合并運算符 與 可選鍊 相結合,可以很輕松處理多級查詢并賦予預設值問題。

var level = user.data?.level ?? '暫無等級';      

四:dynamic-import

按需 ​

​import​

​ 提案幾年前就已提出,如今終于能進入 ES 正式規範。這裡個人了解成 "按需" 更為貼切。現代前端打包資源越來越大,打包成幾 M 的 JS 資源已成常态,而往往前端應用初始化時根本不需要全量加載邏輯資源,為了首屏渲染速度更快,很多時候都是按需加載,比如懶加載圖檔等。而這些按需執行邏輯資源都展現在某一個事件回調中去加載。

el.onclick = () {
import(`/path/current-logic.js`)
    .then((module) => {
module.doSomthing();
    })
    .catch((err) => {
// load error;
    })
}      

當然,webpack 目前已很好的支援了該特性。

五:globalThis

JavaScript 在不同的環境擷取全局對象有不同的方式,NodeJS 中通過 ​

​global​

​​, Web 中通過 ​

​window​

​​, ​

​self​

​​ 等,有些甚至通過 ​

​this​

​​ 擷取,但通過 ​

​this​

​​ 是及其危險的,​

​this​

​ 在 JavaScript 中異常複雜,它嚴重依賴目前的執行上下文,這些無疑增加了擷取全局對象的複雜性。

過去擷取全局對象,可通過一個全局函數:

var getGlobal = 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');
};

var globals = getGlobal();

// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/globalThis      

而 ​

​globalThis​

​​ 目的就是提供一種标準化方式通路全局對象,有了 ​

​globalThis​

​後,你可以在任意上下文,任意時刻都能擷取到全局對象。

六:BigInt

JavaScript 中 ​

​Number​

​​ 類型隻能安全的表示​

​-(2^53-1)​

​​至 ​

​2^53-1​

​​ 範的值,即 ​

​Number.MIN_SAFE_INTEGER​

​​ 至 ​

​Number.MAX_SAFE_INTEGER​

​,超出這個範圍的整數計算或者表示會丢失精度。

var num = Number.MAX_SAFE_INTEGER;  // -> 9007199254740991

num = num + 1; // -> 9007199254740992

// 再次加 +1 後無法正常運算
num = num + 1; // -> 9007199254740992

// 兩個不同的值,卻傳回了true
9007199254740992 === 9007199254740993  // -> true      

為解決此問題,ES2020 提供一種新的資料類型:​

​BigInt​

​​。使用 ​

​BigInt​

​ 有兩種方式:

  1. 在整數字面量後面加​

    ​n​

    ​。
var bigIntNum = 9007199254740993n;      
  1. 使用​

    ​BigInt​

    ​ 函數。
var bigIntNum = BigInt(9007199254740);
var anOtherBigIntNum = BigInt('9007199254740993');      

通過 ​

​BigInt​

​, 我們可以安全的進行大數整型計算。

var bigNumRet = 9007199254740993n + 9007199254740993n; // -> -> 18014398509481986n

bigNumRet.toString(); // -> '18014398509481986'      

注意:

  1. ​BigInt​

    ​ 是一種新的資料原始(primitive)類型。
typeof 9007199254740993n; // -> 'bigint'      
  1. 盡可能避免通過調用函數​

    ​BigInt​

    ​ 方式來執行個體化超大整型。因為參數的字面量實際也是 ​

    ​Number​

    ​ 類型的一次執行個體化,超出安全範圍的數字,可能會引起精度丢失。

七:String.prototype.matchAll

The ​

​matchAll()​

​ method returns an iterator of all results matching a string against a regular expression, including capturing groups. ——MDN

思考下面代碼:

var str = '<text>JS</text><text>正則</text>';
var reg = /<\w+>(.*?)<\/\w+>/g;

console.log(str.match(reg));
// -> ["<text>JS</text>", "<text>正則</text>"]      

可以看出傳回的數組裡包含了父比對項,但未比對到子項(group)。移除全局搜尋符"​

​g​

​"試試。

var str = '<text>JS</text><text>正則</text>';
// 注意這裡沒有全局搜素标示符"g"
var reg = /<\w+>(.*?)<\/\w+>/;
console.log(str.match(reg));

// 上面會列印出
/*
[
    "<text>JS</text>",
    "JS",
    index: 0,
    input:
    "<text>JS</text><text>正則</text>",
    groups: undefined
]
*/      

這樣可以擷取到比對的父項,包括子項(group),但隻能擷取到第一個滿足的比對字元。能看出上面無法比對到​

​<text>正則</text>​

​。

如果擷取到全局所有比對項,包括子項呢?

ES2020 提供了一種簡易的方式:​

​String.prototype.matchAll​

​, 該方法會傳回一個疊代器。

var str = '<text>JS</text><text>正則</text>';
var allMatchs = str.matchAll(/<\w+>(.*?)<\/\w+>/g);

for (const match of allMatchs) {
console.log(match);
}

/*
第一次疊代傳回:
[
    "<text>JS</text>",
    "JS",
    index: 0,
    input: "<text>JS</text><text>正則</text>",
    groups: undefined
]

第二次疊代傳回:
[
    "<text>正則</text>",
    "正則",
    index: 15,
    input: "<text>JS</text><text>正則</text>",
    groups: undefined
]
*/      

參考資料

  1. finished-proposals[1]
  2. TC39 Proposals[2]

繼續閱讀