種草 ES2020 新特性,真的學不動了

  • 2020 年 2 月 24 日
  • 筆記

點擊上方「程式設計師成長指北」,選擇「置頂或者星標」

期待你的關注!

作者:李大雷

https://juejin.im/post/5e09ca40518825499a5abff7

這幾年,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_INTEGERNumber.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]

❤️ 看完三件事

大家好,我是koala,如果你覺得這篇內容對你挺有啟發,我想邀請你幫我三個小忙:

  • 點個【在看】,或者分享轉發,讓更多的人也能看到這篇內容
  • 關注公眾號【程式設計師成長指北】,不定期分享原創&精品技術文章。
  • 添加微信【 coder_qi 】。加入程式設計師成長指北公眾號交流群。