ES10特性詳解

Fundebug發表於2019-02-27

摘要: 最新的JS特性。

Fundebug經授權轉載,版權歸原作者所有。

ES10特性詳解

ES10 還只是一個草案。但是除了 Object.fromEntries 之外,Chrome 的大多數功能都已經實現了,為什麼不早點開始探索呢?當所有瀏覽器都開始支援它時,你將走在前面,這只是時間問題。

在新的語言特性方面,ES10 不如 ES6 重要,但它確實新增了一些有趣的特性(其中一些功能目前還無法在瀏覽器中工作: 2019/02/21)

在 ES6 中,箭頭函式無疑是最受歡迎的新特性,在 ES10 中會是什麼呢?

  • String .matchAll()
  • Dynamic import()
  • Array .flat() .flatMap()
  • Object .fromEntries()
  • String .trimStart() .trimEnd()
  • well-formed JSON.stringify()
  • stable Array .sort()
  • new Function .toString()
  • Standardized globalThis object

BigInt:任意精度整數

BigInt 是第七種 原始型別。

BigInt 是一個任意精度的整數。這意味著變數現在可以 表示²⁵³ 數字,而不僅僅是9007199254740992

const b = 1n;  // 追加 n 以建立 BigInt
複製程式碼

在過去,不支援大於 9007199254740992 的整數值。如果超過,該值將鎖定為 MAX_SAFE_INTEGER + 1:

const limit = Number.MAX_SAFE_INTEGER;
⇨ 9007199254740991
limit + 1;
⇨ 9007199254740992
limit + 2;
⇨ 9007199254740992 <--- MAX_SAFE_INTEGER + 1 exceeded
const larger = 9007199254740991n;
⇨ 9007199254740991n
const integer = BigInt(9007199254740991); // initialize with number9007199254740991n
const same = BigInt("9007199254740991"); // initialize with "string"9007199254740991n
複製程式碼

typeof

typeof 10;
⇨ 'number'
typeof 10n;
⇨ 'bigint'
複製程式碼

等於運算子可用於兩種型別之間比較

10n === BigInt(10);
⇨ true
10n == 10;
⇨ true
複製程式碼

數學運算子只能在自己的型別中工作

200n / 10n
⇨ 20n
200n / 20
⇨ Uncaught TypeError:
   Cannot mix BigInt and other types, use explicit conversions <
複製程式碼

-運算子可以操作, + 不可用

-100n
⇨ -100n
+100n
⇨ Uncaught TypeError:
  Cannot convert a BigInt value to a number
複製程式碼

當你讀到這篇文章的時候,matchAll 可能已經在 Chrome C73 中正式實現了——如果不是,它仍然值得一看。特別是如果你是一個正規表示式(regex)愛好者。

string.prototype.matchAll()

如果您執行谷歌搜尋JavaScript string match all,第一個結果將是這樣的:如何編寫正規表示式“match all”?

最佳結果將建議 String.match 與正規表示式和 /g 一起使用或者帶有 /gRegExp.exec 或者帶有 /gRegExp.test

首先,讓我們看看舊規範是如何工作的。

帶字串引數的 String.match 僅返回第一個匹配:

let string = 'Hello';
let matches = string.match('l');
console.log(matches[0]); // "l"
複製程式碼

結果是單個 "l"(注意:匹配儲存在 matches[0] 中而不是 matches)

“hello”中搜尋 "l" 只返回 "l"

string.matchregex 引數一起使用也是如此:

讓我們使用正規表示式 /l/ 找到字元 串“hello” 中的 “l” 字元:

let string = "Hello";
let matches = string.match(/l/);
console.log(matches[0]); // "l"
複製程式碼

新增 /g 混合

let string = "Hello";
let ret = string.match(/l/g); // (2) [“l”, “l”];
複製程式碼

很好,我們使用 < ES10 方式得到了多個匹配,它一直起作用。

那麼為什麼要使用全新的 matchAll 方法呢? 在我們更詳細地回答這個問題之前,讓我們先來看看 捕獲組。如果不出意外,你可能會學到一些關於正規表示式的新知識。

正規表示式捕獲組

在 regex 中捕獲組只是從 () 括號中提取一個模式,可以使用 /regex/.exec(string)string.match 捕捉組。

常規捕獲組是通過將模式包裝在 (pattern) 中建立的,但是要在結果物件上建立 groups 屬性,它是: (?<name>pattern)

要建立一個新的組名,只需在括號內附加 ?,結果中,分組 (pattern) 匹配將成為 group.name,並附加到 match物件,以下是一個例項:

字串標本匹配:

ES10特性詳解

這裡建立了 match.groups.color 和 match.groups.bird

const string = 'black*raven lime*parrot white*seagull';
const regex = /(?<color>.*?)\*(?<bird>[a-z0-9]+)/g;
while (match = regex.exec(string))
{
    let value = match[0];
    let index = match.index;
    let input = match.input;
    console.log(`${value} at ${index} with '${input}'`);
console.log(match.groups.color);
    console.log(match.groups.bird);
}
複製程式碼

需要多次呼叫 regex.exec 方法來遍歷整個搜尋結果集。 在每次迭代期間呼叫**.exec** 時,將顯示下一個結果(它不會立即返回所有匹配項。),因此使用 while 迴圈。

輸出如下:

black*raven at 0 with 'black*raven lime*parrot white*seagull'
black
raven
lime*parrot at 11 with 'black*raven lime*parrot white*seagull'
lime
parrot
white*seagull at 23 with 'black*raven lime*parrot white*seagull'
white
seagull
複製程式碼

但奇怪的是:

如果你從這個正規表示式中刪除 /g,你將永遠在第一個結果上建立一個無限迴圈。這在過去是一個巨大的痛苦。想象一下,從某個資料庫接收正規表示式時,你不確定它的末尾是否有 /g,你得先檢查一下。

使用 .matchAll() 的好理由

  1. 在與捕獲組一起使用時,它可以更加優雅,捕獲組只是使用 () 提取模式的正規表示式的一部分。
  2. 返回一個迭代器而不是一個陣列,迭代器本身是有用的。
  3. 迭代器可以使用擴充套件運算子 (…) 轉換為陣列。
  4. 它避免了帶有 /g 標誌的正規表示式,當從資料庫或外部源檢索未知正規表示式並與陳舊的RegEx 物件一起使用時,它非常有用。
  5. 使用 RegEx 物件建立的正規表示式不能使用點 (.) 操作符連結。
  6. 高階: RegEx 物件更改跟蹤最後匹配位置的內部 .lastindex 屬性,這在複雜的情況下會造成嚴重破壞。

.matchAll() 是如何工作的?

讓我們嘗試匹配單詞 hello 中字母 el 的所有例項, 因為返回了迭代器,所以可以使用 for…of 迴圈遍歷它:

// Match all occurrences of the letters: "e" or "l" 
let iterator = "hello".matchAll(/[el]/);
for (const match of iterator)
    console.log(match);
複製程式碼

這一次你可以跳過 /g.matchall 方法不需要它,結果如下:

[ 'e', index: 1, input: 'hello' ] // Iteration 1
[ 'l', index: 2, input: 'hello' ] // Iteration 2
[ 'l', index: 3, input: 'hello' ] // Iteration 3
複製程式碼

使用 .matchAll() 捕獲組示例:

.matchAll 具有上面列出的所有好處。它是一個迭代器,可以用 for…of 迴圈遍歷它,這就是整個語法的不同。

const string = 'black*raven lime*parrot white*seagull';
const regex = /(?<color>.*?)\*(?<bird>[a-z0-9]+)/;
for (const match of string.matchAll(regex)) {
    let value = match[0];
    let index = match.index;
    let input = match.input;
    console.log(`${value} at ${index} with '${input}'`);
console.log(match.groups.color);
    console.log(match.groups.bird);
}
複製程式碼

請注意已經沒有 /g 標誌,因為 .matchAll() 已經包含了它,列印如下:

black*raven at 0 with 'black*raven lime*parrot white*seagull'
black
raven
lime*parrot at 11 with 'black*raven lime*parrot white*seagull'
lime
parrot
white*seagull at 23 with 'black*raven lime*parrot white*seagull'
white
seagull
複製程式碼

也許在美學上它與原始正規表示式非常相似,執行while迴圈實現。但是如前所述,由於上面提到的許多原因,這是更好的方法,移除 /g 不會導致無限迴圈。

動態匯入

現在可以將匯入分配給變數:

element.addEventListener('click', async() => {
  const module = await import(`./api-scripts/button-click.js`);
  module.clickEvent();
})
複製程式碼

Array.flat()

扁平化多維陣列:

let multi = [1,2,3,[4,5,6,[7,8,9,[10,11,12]]]];
multi.flat();               // [1,2,3,4,5,6,Array(4)]
multi.flat().flat();        // [1,2,3,4,5,6,7,8,9,Array(3)]
multi.flat().flat().flat(); // [1,2,3,4,5,6,7,8,9,10,11,12]
multi.flat(Infinity);       // [1,2,3,4,5,6,7,8,9,10,11,12]
複製程式碼

Array.flatMap()

let array = [1, 2, 3, 4, 5];
array.map(x => [x, x * 2]);


let array = [1, 2, 3, 4, 5];
array.map(x => [x, x * 2]);
複製程式碼

結果:

[Array(2), Array(2), Array(2), Array(2), Array(2)]
0: (2) [1, 2]
1: (2) [2, 4]
2: (2) [3, 6]
3: (2) [4, 8]
4: (2) [5, 10]
複製程式碼

使用 flatMap 方法:

array.flatMap(v => [v, v * 2]);
[1, 2, 2, 4, 3, 6, 4, 8, 5, 10]
複製程式碼

Object.fromEntries()

將鍵值對列表轉換為物件:

let obj = { apple : 10, orange : 20, banana : 30 };
let entries = Object.entries(obj);
entries;
(3) [Array(2), Array(2), Array(2)]
 0: (2) ["apple", 10]
 1: (2) ["orange", 20]
 2: (2) ["banana", 30]
let fromEntries = Object.fromEntries(entries);
{ apple: 10, orange: 20, banana: 30 }
複製程式碼

String.trimStart() 與 String.trimEnd()

let greeting = "     Space around     ";
greeting.trimEnd();   // "     Space around";
greeting.trimStart(); // "Space around     ";
複製程式碼

格式良好的 JSON.stringify()

此更新修復了字元 U+D800U+DFFF 的處理,有時可以進入 JSON 字串。 這可能是一個問題,因為 JSON.stringify可能會將這些數字格式化為沒有等效 UTF-8 字元的值, 但 JSON 格式需要 UTF-8 編碼。

解析方法使用格式良好的JSON字串,如:

'{ “prop1” : 1, "prop2" : 2 }'; // A well-formed JSON format string
複製程式碼

注意,要建立正確 JSON 格式的字串,絕對需要在屬性名周圍加上雙引號。缺少或任何其他型別的引號都不會生成格式良好的JSON。

'{ “prop1” : 1, "meth" : () => {}}'; // Not JSON format string
複製程式碼

JSON 字串格式與 Object Literal 不同,後者看起來幾乎一樣,但可以使用任何型別的引號括住屬性名,也可以包含方法(JSON格式不允許使用方法):

let object_literal = { property: 1, meth: () => {} };

複製程式碼

不管怎樣,一切似乎都很好。第一個示例看起來是相容的。但它們也是簡單的例子,大多數情況下都能順利地工作!

U+2028 和 U+2029 字元

問題是, ES10 之前的 EcmaScript 實際上並不完全支援 JSON 格式。前 ES10 時代不接受未轉義行分隔符 U+2028 和段落分隔符 U+2029 字元:

ES10特性詳解

ES10特性詳解

對於 U+D800 - U+DFFF 之間的所有字元也是如此

如果這些字元潛入 JSON 格式的字串(假設來自資料庫記錄),你可能會花費數小時試圖弄清楚為什麼程式的其餘部分會產生解析錯誤。

因此,如果你傳遞 eval 這樣的字串 “console.log(' hello ')”,它將執行 JavaScript語句 (通過嘗試將字串轉換為實際程式碼),也類似於 JSON.parse 將處理你的 JSON 字串的方式。

穩定的 Array.prototype.sort()

V8 之前的實現對包含10個以上項的陣列使用了一種不穩定的快速排序演算法。

一個穩定的排序演算法是當兩個鍵值相等的物件在排序後的輸出中出現的順序與在未排序的輸入中出現的順序相同時。

但情況不再是這樣了,ES10 提供了一個穩定的陣列排序:

var fruit = [
    { name: "Apple",      count: 13, },
    { name: "Pear",       count: 12, },
    { name: "Banana",     count: 12, },
    { name: "Strawberry", count: 11, },
    { name: "Cherry",     count: 11, },
    { name: "Blackberry", count: 10, },
    { name: "Pineapple",  count: 10, }
];
// 建立排序函式:
let my_sort = (a, b) => a.count - b.count;
// 執行穩定的ES10排序:
let sorted = fruit.sort(my_sort);
console.log(sorted);
複製程式碼

控制檯輸出(專案以相反的順序出現):

ES10特性詳解

程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug

新的Function.toString()

函式是物件,並且每個物件都有一個 .toString() 方法,因為它最初存在於Object.prototype.toString() 上。 所有物件(包括函式)都是通過基於原型的類繼承從它繼承的。

這意味著我們以前已經有 funcion.toString() 方法了。

但是 ES10 進一步嘗試標準化所有物件和內建函式的字串表示。 以下是各種新案例:

典型的例子

function () { console.log('Hello there.'); }.toString();
複製程式碼

控制檯輸出(函式體的字串格式:)

function () { console.log('Hello there.'); }
複製程式碼

下面是剩下的例子:

直接在方法名 .toString()

Number.parseInt.toString();
⇨ function parseInt() { [native code] }
複製程式碼

繫結上下文

function () { }.bind(0).toString();
⇨ function () { [native code] }
複製程式碼

內建可呼叫函式物件

Symbol.toString();
⇨ function Symbol() { [native code] }
複製程式碼

動態生成的函式

function* () { }.toString();
⇨ function* () { }
複製程式碼

prototype.toString

Function.prototype.toString.call({});
⇨ Function.prototype.toString requires that 'this' be a Function"
複製程式碼

可選的 Catch Binding

在過去,try/catch 語句中的 catch 語句需要一個變數。 try/catch 語句幫助捕獲終端級別的錯誤:

try {
    // Call a non-existing function undefined_Function
    undefined_Function("I'm trying");
}
catch(error) {
    // Display the error if statements inside try above fail
    console.log( error ); // undefined_Function is undefined
}
複製程式碼

在某些情況下,所需的錯誤變數是未使用的:

try {
    JSON.parse(text); // <--- this will fail with "text not defined"
    return true; <--- exit without error even if there is one
}
catch (redundant_sometmes) <--- this makes error variable redundant
{
    return false;
}
複製程式碼

編寫此程式碼的人通過嘗試強制 true 退出 try 子句。但是,這並不是實際發生的情況

(() => {
    try {
        JSON.parse(text)
        return true
    } catch(err) {
        return false
    }
})()
=> false
複製程式碼

在 ES10 中,捕獲錯誤的變數是可選的

現在可以跳過錯誤變數:

try {
    JSON.parse(text);
    return true;
}
catch
{
    return false;
}
複製程式碼

目前還無法測試上一個示例中的 try 語句的結果,但一旦它出來,我將更新這部分。

標準化 globalThis 物件

這在ES10之前, globalThis 還沒有標準化。

在產品程式碼中,你可以自己編寫這個怪物,在多個平臺上“標準化”它:

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');
};
複製程式碼

但即使這樣也不總是奏效。因此,ES10 新增了 globalThis 物件,從現在開始,該物件用於在任何平臺上訪問全域性作用域:

// 訪問全域性陣列建構函式
globalThis.Array(0, 1, 2);
⇨ [0, 1, 2]

// 類似於 ES5 之前的 window.v = { flag: true }
globalThis.v = { flag: true };

console.log(globalThis.v);
⇨ { flag: true }
複製程式碼

Symbol.description

description 是一個只讀屬性,它返回 Symbol 物件的可選描述。

let mySymbol = 'My Symbol';
let symObj = Symbol(mySymbol);
symObj; // Symbol(My Symbol)
symObj.description; // "My Symbol"
複製程式碼

Hashbang 語法

也就是 unix 使用者熟悉的 shebang。它指定一個直譯器(什麼將執行JavaScript檔案?)。

ES10標準化,我不會對此進行詳細介紹,因為從技術上講,這並不是一個真正的語言特性,但它基本上統一了 JavaScript 在伺服器端的執行方式。

$ ./index.js
複製程式碼

代替

$ node index.js
複製程式碼

ES10類: private、static 和 公共成員

新的語法字元 #octothorpe(hash tag)現在用於直接在類主體的範圍內定義變數,函式,getter 和 setter ......以及建構函式和類方法。

下面是一個毫無意義的例子,它只關注新語法:

class Raven extends Bird {
#state = { eggs: 10};
// getter
    get #eggs() { 
        return state.eggs;
    }
// setter
    set #eggs(value) {
        this.#state.eggs = value;
    }
#lay() {
        this.#eggs++;
    }
constructor() {
        super();
        this.#lay.bind(this);
    }
#render() {
        /* paint UI */
    }
}
複製程式碼

老實說,我認為這會讓語言更難讀。

原文:The Complete Guide to ES10 Features

關於Fundebug

Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了9億+錯誤事件,付費客戶有Google、360、金山軟體、百姓網等眾多品牌企業。歡迎大家免費試用

ES10特性詳解

相關文章