ES2020,你須要知道的一切內容!

譯原文:www.martinmck.com/posts/es202…javascript

做者:Martin McKeaveney前端

譯:黃梵高java

如何給JavaScript增長新的特性?

並不是是Google,或者是其餘權力巔峯的人。JavaScript規範由稱爲TC39的委員會進行管理和迭代。TC39由各類開發人員,學術界人士和平臺愛好者組成。webpack

TC39每一年約召開6次會議,大部分在美國,但在歐洲也舉行。他們與社區合做,接受有關JavaScript新功能的建議,並逐步處理JavaScript語言建議的四個「階段」。這四個階段以下:git

Stage 0: strawman 一種推動ECMAScript發展的自由形式,任何TC39成員,或者註冊爲TC39貢獻者的會員,均可以提交。一般經過針對TC39 ECMAScript GitHub存儲庫提升PR來完成此操做github

Stage 1: proposal 該階段產生一個正式的提案。web

  • 肯定一個帶頭人來負責該提案,帶頭人或者聯合帶頭人必須是TC39的成員。
  • 描述清楚要解決的問題,解決方案中必須包含例子,API以及關於相關的語義和算法。
  • 潛在問題也應該指出來,例如與其餘特性的關係,實現它所面臨的挑戰。
  • polyfill和demo也是必要的。

圍繞提案建立了一個公共GitHub存儲庫,其中包含示例,高級API,基本原理和潛在問題。正則表達式

Stage 2: draft 草案是規範的第一個版本,與最終標準中包含的特性不會有太大差異。 草案以後,原則上只接受增量修改。算法

  • 草案中包含新增特性語法和語義的,儘量的完善的形式說明,容許包含一些待辦事項或者佔位符。
  • 必須包含2個實驗性的具體實現,其中一個能夠是用轉譯器實現的,例如Babel。

「草案」階段意味着須要肯定提案的全部語法和語義。這涉及使用您將在JavaScript規範自己中看到的正式規範語言描述提案功能。typescript

Stage 3: candidate 候選階段,得到具體實現和用戶的反饋。 此後,只有在實現和使用過程當中出現了重大問題纔會修改。

  • 規範文檔必須是完整的,評審人和ECMAScript的編輯要在規範上簽字。
  • 至少要有兩個符合規範的具體實現

在此階段,責任在於社區。開發人員應使用該功能並提供反饋,這隻有經過在實際開發中使用它才能實現。

Stage 4: finished 已經準備就緒,該特性會出如今年度發佈的規範之中。

  • 經過Test 262驗收測試
  • 有2個經過測試的實現,以獲取使用過程當中的重要實踐經驗。
  • ECMAScript的編輯必須規範上的簽字。

塵埃落定。該建議已在社區中的實際實施中獲得了很好的測試。本提案將包含在下一版ECMAScript標準中,並將被數以百萬計的人使用。

下面就是es2020新特性的介紹!

String.prototype.matchAll

String.prototype.matchAll是一個實用函數,用於獲取特定正則表達式的全部匹配項(包括捕獲組,這將在後面說明)。在ES2020以前如何解決此問題?讓咱們來看一個簡單的示例並進行迭代:

const test = "climbing, oranges, jumping, flying, carrot";
複製代碼

目標是獲取全部以ing結尾的單詞,而且返回他們去掉ing的動詞格式

  • 在字符串中搜索以「 ing」結尾的任何單詞(例如「 climbing」)
  • 捕獲單詞中「 ing」以前的全部字母(例如「 climb」)
  • 返回字符串

爲了達到目的,咱們使用下面的正則:

const regex = /([a-z]*)ing/g;
複製代碼

正則表達式很難,讓咱們對其分解,瞭解每一部分的原理。

  • ([a-z]*)-匹配任何連續包含字母a到z的字符串。咱們將其包裝在括號中(),以使其成爲「捕獲組」。顧名思義,捕獲組就是「捕獲」與該特定部分匹配的字符組。在咱們的示例中,咱們但願匹配全部以「 ing」結尾的單詞,可是咱們真正想要的是以前的字母,所以使用捕獲組。
  • ing -僅匹配以「 ing」結尾的字符串。
  • /g-全局搜索。搜索整個輸入字符串。不要在第一次匹配成功後就停下來。

String.prototype.match

咱們但願經過正則表達式查找動詞。JavaScript中的match函數能夠傳入正則表達式。

const test = "climbing, oranges, jumping, flying, carrot";
const regex = /([a-z]*)ing/g;

test.match(regex);

// ["climbing", "jumping", "flying"]
複製代碼

但這並非咱們想要的。它返回了完整的單詞,而不僅是動詞!發生這種狀況是由於match不支持使用/g標誌捕獲組。match在不須要使用捕獲組的時候很好用,但如今彷佛不太適合。

RegExp.prototype.exec

exec方法在正則表達式自己上執行,而不是在相似字符串上執行matchexec支持捕獲組,可是使用的API稍顯笨拙。您必須不斷exec在正則表達式上反覆調用以獲取下一個匹配項。這要求咱們建立一個無限循環並持續調用,exec執行到沒有匹配項爲止。

const regex = /([a-z]*)ing/g;

const matches = [];

while (true) {
  const match = regex.exec(test);
  if (match === null) break;
  matches.push(match[1]);
}

matches
// ["climb", "jump", "fly"]
複製代碼

這種方法是能夠知足需求的,可是有點混亂不清晰。這樣作的主要緣由有兩個:

  • 僅當/g標誌設置在末尾時,它才執行預期的操做。若是您將正則表達式做爲變量或參數傳遞,這可能會引發混淆。
  • 使用/g標誌時,RegExp對象是有狀態的,並存儲對其最後匹配項的引用。而這一點有可能致使bug,若是你重複且不一樣的調用它。

使用String.prototype.matchAll

終於,咱們到了。String.prototype.matchAll這將使咱們的生活更加輕鬆,並提供一個簡單的解決方案來支持捕獲組,並返回一個可迭代的數組,該數組能夠擴展爲數組。讓咱們將上面的代碼重構使用matchAll。

const test = "climbing, oranges, jumping, flying, carrot";

const regex = /([a-z]*)ing/g;

const matches = [...test.matchAll(regex)];

const result = matches.map(match => match[1]);

result

// ["climb", "jump", "fly"]
複製代碼

咱們獲得一個二維數組,第一個元素中的單詞全匹配**('climbing'),第二個元素中的捕獲組('climb')**。經過迭代並保留第二個元素,終於咱們獲得想要的結果。

動態import()

因爲webpack的支持,這是您可能已經熟悉的一種方式。而且在生產JavaScript應用程序中常用它來進行「代碼拆分」。代碼拆分在單個頁面應用程序中很是強大。在許多狀況下,能夠大大加快初始頁面加載時間。

動態導入語法容許咱們將import做爲可以返回promise的函數進行調用。這對於在代碼運行時動態加載模塊特別有用。例如,您可能想基於代碼中的某些邏輯來加載某個組件或模塊。

// JavaScript for side panel is loaded
  const sidePanel = await import("components/SidePanel");
  sidePanel.open()
複製代碼

還支持插值。

async function openSidePanel(type = "desktop") {
    // JavaScript for desktop side panel is loaded
    const sidePanel = await import(`components/${type}/SidePanel`);
    sidePanel.open();
}
複製代碼

這個新功能加強了咱們應用程序的性能。咱們沒必要預先加載全部的JavaScript。動態導入使咱們可以僅加載所需數量的JS控件,性能上極大提高。

BigInt

JavaScript能夠處理的最大數量爲2^53。就是這樣9007199254740991,或者您可使用更好記一點的Number.MAX_SAFE_INTEGER

當你數字超過MAX_SAFE_INTEGER時會發生什麼?

console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992 - wut
console.log(Number.MAX_SAFE_INTEGER + 3); // 9007199254740994 - WUT
複製代碼

BigInt是ES2020中新增的類型,用於解決此問題。要將number或者string轉換爲BigInt,可使用BigInt構造函數,也能夠n在其末尾添加a 。所以,要修正上面的示例,在將2加到後獲得相同的值Number.MAX_SAFE_INTEGER

BigInt(Number.MAX_SAFE_INTEGER) + 2n; // 9007199254740993n ✅
複製代碼

誰須要這些數字呢?

您可能會驚訝於在軟件開發中擁有如此龐大的數字很是廣泛。好比時間戳和惟一標識符就能夠是這麼大的數字。

例如,Twitter使用如此大的整數做爲推文的惟一鍵。若是您嘗試將JavaScript存儲爲不帶數字的數字,則會在JavaScript應用程序中看到奇怪的錯誤BigInt。您將不得不使用第三方社區包,或者將它們存儲爲字符串。這也是JavaScript開發人員在BigInt不支持的環境中解決此問題的經常使用解決方案。

Promise.allSettled

假設您正在參加考試。收到結果後,您發現您正確回答了99%的問題。在大多數生活領域中,您都會充滿對勝利的但願。不過,在這種狀況下,您依然會在得分中收到一個大紅色的叉,告訴您您失敗了。

這就是Promise.all的工做方式。Promise.all接受一系列承諾,並同時獲取其結果。若是它們所有成功,您的Promise.all成功。若是一項或多項失敗,您的Promise就會被reject。在某些狀況下,您可能須要這種處理方式,但事實上並不老是這樣。

引入Promise.allSettled

ES2020的Promise.allSettled在考試方面可要比Promise.all好得多。它將使您輕拍一下,並告訴您不要擔憂1%的reject

Promise出現時,不管它被認爲是resolverejectPromise.allSettled容許咱們傳遞一系列的Promise,這些Promise將在所有結束後,Promise的返回值是一個裝滿Promise結果的數組。讓咱們看一個例子:

const promises = [
  fetch('/api1'),
  fetch('/api2'),
  fetch('/api3'),
];

Promise.allSettled(promises).
  then((results) => results.forEach((result) => console.log(result.status)));

// "fulfilled"
// "fulfilled"
// "rejected"
複製代碼

globalThis

globalThis 是一個全新的標準方法用來獲取全局 this 。以前開發者會經過以下的一些方法獲取:

  • 全局變量 window:是一個經典的獲取全局對象的方法。可是它在 Node.js 和 Web Workers 中並不能使用
  • 全局變量 self:一般只在 Web Workers 和瀏覽器中生效。可是它不支持 Node.js。一些人會經過判斷 self - 是否存在識別代碼是否運行在 Web Workers 和瀏覽器中
  • 全局變量 global:只在 Node.js 中生效

因爲Js的通用性,相同的JavaScript代碼能夠在NodeJS的客戶端和服務器上運行。這提出了一系列特殊的挑戰。

一個是全局對象,能夠從任何運行的代碼段中訪問它。window在瀏覽器中,但globalNode中。編寫訪問此全局對象的通用代碼依賴於一些條件邏輯,這些條件邏輯可能看起來像這樣(矇住眼睛)。

(typeof window !== "undefined"
? window
: (typeof process === 'object' &&
   typeof require === 'function' &&
   typeof global === 'object')
    ? global
    : this);
複製代碼

值得慶幸的是,ES2020帶來了globalThis全局變量。如今能夠放鬆的不去考慮windowglobal而統一前端或後端代碼。

globalThis.something = "Hello"; // Works in Browser and Node.
複製代碼

for-in的學問

for (x in obj) ... 是在不少時候都超級有用的語法,主要是遍歷對象的key值。

for (let key in obj) {
  console.log(key);                      
}
複製代碼

該提議與在循環中迭代元素的順序和語義有關。在提出這個以前,大多數JavaScript引擎已經應用了常識,全部主流瀏覽器都按照定義它們的順序遍歷對象的屬性。可是,有些細微差異。這些主要涉及更高級的功能,例如代理。for..in循環語義從一開始就沒有包含在JavaScript規範中,可是該提議可確保每一個人在for..in工做方式上都具備一致的參考點。

可選鏈操做符(Optional Chaining)

可選連接多是至關長一段時間以來JavaScript中最受期待的功能之一。在對更乾淨的JavaScript代碼的影響方面,這一得分很是高!!!

當檢查嵌套對象內部的屬性時,一般必須檢查中間對象的存在。讓咱們來看一個例子:

const test = {
  name: "foo",
  age: 25,
  address: {
    number: 44,
    street: "Sesame Street",
    city: {
      name: "Fake City",
      lat: 40,
      lon: 74
    }
  }
}

// when we want to check for the name of the city
if (test.address.city.name) {
  console.log("City name exists!");
}

// City Name exists!
複製代碼

這是一個成功的例子!可是在開發中,不能老是依靠幸福的曙光照耀咱們。有時中間的值會不存在。讓咱們看一樣的例子,可是沒有給city定義值。

const test = {
  name: "foo",
  age: 25,
  address: {
    number: 44,
    street: "Sesame Street"
  }
}

if (test.address.city.name) {
  console.log("City name exists!");
}

// TypeError: Cannot read property 'name' of undefined
複製代碼

咱們的代碼已經報錯了。這是由於咱們試圖訪問nametest.address.city,可是這是一個undefined。當嘗試讀取undefined上的屬性時TypeError確定會拋出以上內容。那咱們該如何解決?在以前許多JavaScript代碼中,您將看到如下解決方案:

const test = {
  name: "foo",
  age: 25,
  address: {
    number: 44,
    street: "Sesame Street"
  },
  
}

if (test.address && test.address.city && test.address.city.name) {
  console.log("City name exists!");
}

// no TypeError thrown!
複製代碼

咱們的代碼如今能夠運行了,可是咱們不得不在出bug那兒寫不少代碼來解決問題。咱們按理來講能夠作得更好!ES2020的可選連接運算符使您可使用新的?語法檢查對象深處是否存在值。這是使用可選連接運算符重寫的上述示例:

const test = {
  name: "foo",
  age: 25,
  address: {
    number: 44,
    street: "Sesame Street"
  },
  
}

// much cleaner.
if (test?.address?.city?.name) {
  console.log("City name exists!");
}

// no TypeError thrown!
複製代碼

看起來不錯。咱們將十分長的&&鏈,濃縮爲更加簡潔易讀的可選鏈運算符。若是鏈中的任何值是null或者 undefined,則表達式僅返回undefined

可選的連接運算符很是強大。請看如下示例可使用它的其餘方式:

const nestedProp = obj?.['prop' + 'Name']; // computed properties

const result = obj.customMethod?.(); // functions

const arrayItem = arr?.[42]; // arrays
複製代碼

空位合併運算符(Nullish coalescing Operator)

空位合併運算符是一個很是簡單的名稱,聽起來很花哨。此功能使咱們可以檢查一個值是否爲nullundefined,若是是,則默認爲另外一個值,僅此而已。

爲何這有用?讓咱們假設一下,你的JavaScript中有五個虛值。

  • null
  • undefined
  • 空字符串 ""
  • 0
  • 沒有數字-NaN

咱們可能有一些要檢查數值的代碼。咱們想爲球隊中的球員分配一個小隊號碼。若是他們已經有小隊號碼,咱們將保留該號碼。不然,我給他們一個叫"unssigned"的值。

const person = {
  name: "John",
  age: 20,
  squadNumber: 100
};

const squadNumber = person.squadNumber || "unassigned";

console.log(`${person.name}s squad number is ${squadNumber}`);


// "Johns squad number is 100"
複製代碼

此代碼能夠正常工做。可是,讓咱們從一個稍微不一樣的角度來考慮。若是咱們的person0,該怎麼辦?

const person = {
  name: "Dave",
  age: 30,
  squadNumber: 0
};

const squadNumber = person.squadNumber || "unassigned";

console.log(`${person.name}s squad number is ${squadNumber}`);


// "Daves squad number is unassigned"
複製代碼

這是不對的。Dave已經爲球隊效力了多年。咱們的代碼有一個錯誤。發生這種狀況是由於0,致使咱們false條件的||被調用。在此示例中,對結果值的檢查存在問題。您固然能夠經過如下操做解決此問題:

const person = {
  name: "Dave",
  age: 30,
  squadNumber: 0
};

const squadNumber = person.squadNumber >= 0 ? person.squadNumber : "unassigned";

console.log(`${person.name}s squad number is ${squadNumber}`);


// "Daves squad number is 0"
複製代碼

這是一個還能夠的解決方案,但咱們可使用空位合併運算符來解決。

運算符??能夠更好地確保咱們的值是nullundefined

const person = {
  name: "Dave",
  age: 30,
  squadNumber: 0
};

// Nullish Coalescing Operator
// If person.squadNumber is null or undefined
// set squadNumber to unassigned
const squadNumber = person.squadNumber ?? "unassigned";

console.log(`${person.name}s squad number is ${squadNumber}`);


// "Daves squad number is 0"
複製代碼

import.meta

import.meta是一個給JavaScript模塊暴露特定上下文的元數據屬性的對象。它包含了這個模塊的信息,好比說這個模塊的URL。

若是您熟悉Node,則能夠經過__dirname__filename屬性與CommonJS一塊兒使用此功能。

const fs = require("fs");
const path = require("path");
// resolves data.bin relative to the directory of this module
const bytes = fs.readFileSync(path.resolve(__dirname, "data.bin"));
複製代碼

那瀏覽器呢?這裏import.meta將會變得有用。若是要從瀏覽器中運行的JavaScript模塊導入相對路徑,則可讓import.meta這樣作:

// Will import cool-image relative to where this module is running.
const response = await fetch(new URL("../cool-image.jpg", import.meta.url));
複製代碼

此功能對開發第三方庫的做者那但是很是有用了,由於他們不知道他們的代碼將在什麼地方以什麼方式運行的。

結論

總而言之,ECMAScript規範中添加的最新功能爲不斷髮展和發展的JavaScript生態系統增長了更多實用性,靈活性和強大功能。看到社區繼續以如此快的速度蓬勃發展和進步,真的使人鼓舞和激動。

您可能在想:「這些聽起來不錯,可是我如何在個人項目裏使用ES2020功能呢?

何時,如何使用這些東西?

如今就可使用它!在大多數現代瀏覽器和Node的最新版本中,支持全部的功能。caniuse.com是另外一個很棒的網站,可用於檢查跨瀏覽器和Node的ES2020新增功能的兼容性級別。

若是須要在較舊的瀏覽器或Node版本上使用這些功能,則須要babel / typescript

但願你學到了一些東西。謝謝閱讀!

參考文獻:

  1. github.com/tc39/propos…
  2. prop-tc39.now.sh/
  3. www.martinmck.com/posts/es202…
相關文章
相關標籤/搜索