Javascript 的新功能-Part 1[每日前端夜話0xC6]

  • 2019 年 10 月 7 日
  • 筆記

作者:Deepak Gupta

翻譯:瘋狂的技術宅

來源:medium

JavaScript 的應用領域已經從 Web 瀏覽器擴展到所有需要編程的地方。

  1. Node.js — 用於CLI和伺服器。
  2. Electron — 用於跨平台的桌面應用程式。
  3. React native — 用於跨平台的移動應用。
  4. IoT — 低成本物聯網設備現在開始支援 javascript。

最近更新的 V8 引擎使性能提升了不少。JavaScript 解析速度提高了 2 倍甚至更快,從node v8.0開始,node v11以上版本的平均速度比 node v8.0 提高了 11 倍。記憶體消耗減少了 20%。在性能和可用性上有了全面改善。

在本文中,我們將看到一些可以在Chrome瀏覽器(版本 ≥ 76)或 Node.js(版本 ≥ 11)CLI 中測試的 ES10 強大功能。

私有類欄位

在ES6之前,我們無法直接申請 private 屬性。是的,有下劃線約定(_propertyName)、閉包、 symbols 或 WeakMaps 等方法。

但現在私有類欄位可以使用哈希前綴 # 來定義。讓我們通過實例學習它

class Test {    a = 1;          // .a is public    #b = 2;         // .#b is private    static #c = 3;  // .#c is private and static    incB() {      this.#b++;    }  }  const testInstance = new Test();  // runs OK  testInstance.incB();  // error - private property cannot be modified outside class  testInstance.#b = 0;  

注意:截至目前,沒有辦法定義私有函數,儘管 TC39 第 3 階段:建議草案【https://github.com/tc39/proposal-private-methods】建議在名字上使用散列前綴?

String.matchAll()?

如果我有一個字元串,其中有多個全局正則表達式捕獲組,我經常想要遍歷所有匹配。目前,我的選擇有以下幾種:

  1. RegExp.prototype.exec() with /g — 我們可以稱之為 .exec() 多次獲得一個正則表達式的匹配。它為每個匹配返回一個匹配對象,最後返回 null。
  2. String.prototype.match() with /g — 如果我們通過 .match() 使用正則表達式,設置其標誌為 /g ,你會得到一個完全匹配的數組。
  3. String.prototype.split() — 如果我們使用分割字元串和正則表達式來指定分隔符,並且它至少包含一個捕獲組,那麼 .split() 將返回一個子串交錯的數組。

上述方法的問題在於,只有在正則表達式上設置 /g 並且每次匹配時對正則表達式的屬性 .lastIndex 進行更改時,它們才起作用。這使得在多個位置使用相同的正則表達式存在風險。

matchAll() 能夠幫助解決以上所有問題。讓我們看看它的定義和使用:

給定字元串和正則表達式,.matchAll() 返回與正則表達式匹配的所有結果,包括捕獲組。

let regexp = /t(e)(st(d?))/g;  let str = 'test1test2';let array = [...str.matchAll(regexp)];console.log(array[0]);  // expected output: Array ["test1", "e", "st1", "1"]  

注意:.matchAll() 返回一個迭代器,但它不是真正的可重啟迭代器。也就是說一旦結果耗盡,則需要再次調用該方法並創建一個新的迭代器。

數字分隔?

如果你一直在努力去讀較長的數字序列,那麼這就是你要找的。

數字分隔符使人眼能夠快速解析,尤其是當有很多重複的數字時:

1000000000000 -> 1_000_000_000_000  1019436871.42 -> 1_019_436_871.42  

現在,更容易說出第一個數字是1萬億,而第二個數字是10億。

這也適用於其他進位,例如:

const fileSystemPermission = 0b111_111_000;  const bytes = 0b1111_10101011_11110000_00001101;  const words = 0xFAB_F00D;  

你還可以用在分數和指數中:

const massOfElectronInKg = 9.109_383_56e-31;  const trillionInShortScale = 1e1_2;  

注意:解析帶有 _ 分隔的整數可能很棘手,因為Number('123_456') 會給出 NAN,而 parseInt('123_456') 則給出 123

BigInt?

BigInts 是 JavaScript 中的一種新的數字原語,可以表示精度比2⁵³-1更大的整數。使用 BigInts,你可以安全地存儲和操作大整數,甚至可以超出 Numbers 的安全整數限制。

BigInts 可以正確執行整數運算而不會溢出。讓我們通過一個例子來理解:

const max = Number.MAX_SAFE_INTEGER;  // 9007199254740991  max+1;  // 9007199254740992  max+2;  // 9007199254740992  

我們可以看到 max + 1 產生的結果與 max + 2 相同。

任何超出安全整數範圍(即從 Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER)的整數的計算都可能會失去精度。所以我們只能依賴安全範圍內的數字整型的值。

BigInts 應運而生,可以通過將 n 後綴添加到整數文字中來創建 BigInts 。例如,123 變成 123n,或者全局 BigInt(number) 函數可用於將 Number 轉換為 BigInts

讓我們重新看一下上面的 BigInt 例子

BigInt(Number.MAX_SAFE_INTEGER) + 2n;  // 9007199254740993ntypeof 123n  // "bigint2"  

注意:數字分隔符對於BigInts尤其有用,例如: const massOfEarthInKg = 6_000_000_000_000_000_000_000_000n;

BigInts 支援最常見的運算符。二進位 +-*** 均按預期工作。/ 工作時根據需要四捨五入。

(7 + 6 - 5) * 4 ** 3 / 2 % 3;  // → 1  (7n + 6n - 5n) * 4n ** 3n / 2n % 3n;  // → 1n  

注意:它不允許在 BigIntsNumbers 之間進行混合運算。

BigInt 的語言環境字元串?

toLocaleString() 方法返回一個字元串,該字元串具有 BigInt 的語言敏感表示形式。

let bigint = 123456789123456789n;    // 德國使用 thousands  console.log(bigint.toLocaleString('de-DE'));  // → 123.456.789.123.456.789    //在大多數說阿拉伯語的國家中,阿拉伯語使用東部阿拉伯數字  console.log(bigint.toLocaleString('ar-EG'));  // → ١٢٣٬٤٥٦٬٧٨٩٬١٢٣٬٤٥٦٬٧٨٩    // 印度使用 thousands/lakh/crore 分隔符  console.log(bigint.toLocaleString('en-IN'));  // → 1,23,45,67,89,12,34,56,789    // nu 擴展用於請求編號系統,例如 中文數字  console.log(bigint.toLocaleString('zh-Hans-CN-u-nu-hanidec'));  // → 一二三,四五六,七八九,一二三,四五六,七八九    // 請求不支援的語言(例如巴厘語)時,請使用後備語言(在這種情況下為印尼語)  console.log(bigint.toLocaleString(['ban', 'id']));  // → 123.456.789.123.456.789  

globalThis 關鍵字?

JavaScript 的變數作用域被嵌套並形成樹結構,其根是全局作用域,this 關鍵字的值是對 「擁有」 當前正在執行的程式碼或所查看函數的對象的引用。

要了解有關此關鍵字和全局作用一的更多資訊,請閱讀以下文章

Scopes in Javascript【https://medium.com/datadriveninvestor/still-confused-in-js-scopes-f7dae62c16ee】 Understanding Javascript 『this』 keyword (Context)【https://medium.com/datadriveninvestor/javascript-context-this-keyword-9a78a19d5786】

通常要弄清楚全局作用域,我們使用這樣的函數

const getGlobalThis = () => {      // 在 webworker 或 service worker 中    if (typeof self !== 'undefined') return self;      // 在瀏覽器中    if (typeof window !== 'undefined') return window;      // 在 Node.js 中    if (typeof global !== 'undefined') return global;      // 獨立的 JavaScript shell    if (typeof this !== 'undefined') return this;      throw new Error('Unable to locate global object');  };const theGlobalThis = getGlobalThis();  

以上函數並不涵蓋全局變數的所有情況。

  1. 如果使用strict,則其值是 undefined
  2. 當我們在 javascript 中形成捆綁包時,通常會在一些可能與此全局程式碼不同的程式碼下進行包裝。
  3. 在獨立的 JavaScript 引擎 shell 環境中,以上程式碼將不起作用

為了解決上述問題,引入了 globalThis 關鍵字,該關鍵字可以在任何環境下隨時返回全局對象。

注意:為了保持向後兼容,現在全局對象被認為是 JavaScript 無法消除的錯誤。它會對性能產生負面影響,並經常使人困惑。

Promise.allSettled()?

如果你想知道 JavaScript Promise 的用途,請查看此內容 —— JavaScript Promises:簡介【https://developers.google.com/web/fundamentals/primers/promises】。

Promise 是 JavaScript 向你承諾工作將要完成的方式(如果工作無法完成,則可能會失敗)。

新方法會返回一個 Promise ,它會在所有給定的 Promise 均已解決(即已解決或拒絕)之後解決,並帶有一系列對象,一個對象描述一個 Promise 的結果。

const promise1 = Promise.resolve(3);  const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));  const promises = [promise1, promise2];Promise.allSettled(promises).    then((results) => results.forEach((result) => console.log(result.status)));  // 預期輸出:  // "fulfilled"  // "rejected"  

這與 Promise.all 不同,因為 Promise.all 在可迭代對象中的 Promise 被拒絕後就立即拒絕。

下面是當前支援的 promise 方法的比較

Short-circuit?

Short-circuits on?

Fulfilled on?

Rejected on?

Promise.all

First rejected promise

All promise fulfilled

First rejected promise

Promise.allSettled

N/A

Always

N/A

Promise.race

First settled

First promise fulfilled

First rejected promise

動態導入?

靜態與動態導入

這個很瘋狂,在我們深入研究它之前,先看看靜態導入是什麼。

靜態導入僅接受字元串文字作為模組說明符,並通過運行前的「鏈接」過程將綁定引入本地作用域。

靜態的 import 語法只能在文件的頂層使用。

import * as module from './utils.mjs';  

靜態 import 可以啟用重要的用例,如靜態分析、捆綁工具、和tree-shaking。

但是以下這些:

  • 按需(或有條件)導入模組
  • 在運行時計算模組說明符
  • 從常規腳本(而不是模組)中導入模組

動態導入出現之前是不可能的 — import(moduleSpecifier) 返回所請求模組的模組命名空間對象的promise,它是在提取、實例化和評估模組的所有依賴關係以及模組本身之後才創建的。

<script type="module">    (async () => {      const moduleSpecifier = './utils.mjs';      const module = await import(moduleSpecifier)      module.default();      // → logs 'Hi from the default export!'      module.doStuff();      // → logs 'Doing stuff…'    })();  </script>  

注意:對於初始化繪製依賴項,尤其是首屏內容時請使用靜態 import。在其他情況下,考慮用動態 import()按需載入依賴項。

穩定排序(現在能夠得到一致和可靠的結果)?

穩定在演算法意義【https://en.wikipedia.org/wiki/Sorting_algorithm#Stability】上的意思是:它是保留順序,還是僅保證項目「相等」

讓我們通過一個例子理解它:

const people = [    {name: 'Gary', age: 20},    {name: 'Ann', age: 20},    {name: 'Bob', age: 17},    {name: 'Sue', age: 21},    {name: 'Sam', age: 17},  ];  // Sort people by name  people.sort( (p1, p2) => {    if (p1.name < p2.name) return -1;    if (p1.name > p2.name) return 1;    return 0;  });console.log(people.map(p => p.name));  // ['Ann', 'Bob', 'Gary', 'Sam', 'Sue']  // Re-sort people by age  people.sort( (p1, p2) => {    if (p1.age < p2.age) return -1;    if (p1.age > p2.age) return 1;    return 0;  });console.log(people.map(p => p.name));  // 我們期望先按年齡,然後按年齡組中的姓名排序:  // ['Bob', 'Sam', 'Ann', 'Gary', 'Sue']  // 但是我們可能會得到其中的任何一種,這取決於瀏覽器:  // ['Sam', 'Bob', 'Ann', 'Gary', 'Sue']  // ['Bob', 'Sam', 'Gary', 'Ann', 'Sue']  // ['Sam', 'Bob', 'Gary', 'Ann', 'Sue']  

如果你得到的是最後三個結果之一,則可能是你用的是 Google Chrome 瀏覽器,或者可能是沒有將 Array.sort()實現為「穩定」演算法的各種瀏覽器之一。

這是因為不同的 JS 引擎(在不同的瀏覽器上)採用了不同的路徑來實現排序,而且某些 JavaScript 引擎對短數組使用穩定的排序,而對長數組使用不穩定的排序。

這就導致了因為排序穩定性的行為不一致而引發了很多混亂。這就是為什麼在開發環境中與排序相關的內容似乎都可以工作,但是在生產環境中,由於和測試排序所使用的數組大小不同,我們開始看到其他內容的原因。

注意:有一些第三方庫,我強烈衷心推薦 Lodash,它能夠提供穩定的排序

但這些問題已經解決,我們在大多數瀏覽器上都能得到穩定的排序,同時語法保持不變。

由於本文有很多知識點和需要實際測試的功能,所以我們將在下一篇文章中繼續介紹更多的新功能。

原文:https://medium.com/@ideepak.jsd/javascript-new-features-part-1-f1a4360466