跳至主要内容

39 篇文章 含有標籤「ECMAScript」

檢視所有標籤

弱引用與終結器

· 閱讀時間約 10 分鐘
Sathya Gunasekaran ([@_gsathya](https://twitter.com/_gsathya)), Mathias Bynens ([@mathias](https://twitter.com/mathias)), Shu-yu Guo ([@_shu](https://twitter.com/_shu)), 和 Leszek Swirski ([@leszekswirski](https://twitter.com/leszekswirski))

通常,在 JavaScript 中對物件的引用是 強引用,這意味著只要你擁有對該物件的引用,它就不會被垃圾回收機制回收。

const ref = { x: 42, y: 51 };
// 只要你能訪問 `ref`(或者其他任何對同一物件的引用),該物件就不會被垃圾回收機制回收。

目前,WeakMapWeakSet 是 JavaScript 中唯一可以弱引用物件的方式:將物件作為 WeakMapWeakSet 的鍵添加,並不會防止該物件被垃圾回收機制回收。

const wm = new WeakMap();
{
const ref = {};
const metaData = 'foo';
wm.set(ref, metaData);
wm.get(ref);
// → metaData
}
// 在這個區塊範圍內,我們不再有對 `ref` 的引用,因此它
// 現在可以被垃圾回收機制回收,儘管它是 `wm` 的一個鍵
// 且我們仍然可以訪問 `wm`。

穩定的 `Array.prototype.sort`

· 閱讀時間約 3 分鐘
Mathias Bynens ([@mathias](https://twitter.com/mathias))

假設你有一個狗狗的陣列,每隻狗有一個名字和一個評級。(如果這聽起來是個奇怪的例子,你應該知道,Twitter 上有一個專門做這件事的賬號……請不要深究!)

// 注意,陣列已按 `name` 字母序排序。
const doggos = [
{ name: 'Abby', rating: 12 },
{ name: 'Bandit', rating: 13 },
{ name: 'Choco', rating: 14 },
{ name: 'Daisy', rating: 12 },
{ name: 'Elmo', rating: 12 },
{ name: 'Falco', rating: 13 },
{ name: 'Ghost', rating: 14 },
];
// 按 `rating` 降序排序狗狗。
// (會就地更新 `doggos`。)
doggos.sort((a, b) => b.rating - a.rating);

`Symbol.prototype.description`

· 閱讀時間約 1 分鐘
Mathias Bynens ([@mathias](https://twitter.com/mathias))

JavaScript Symbol 在建立時可以給予一個描述:

const symbol = Symbol('foo');
// ^^^^^

以前,要以程式方式存取這個描述的唯一方法是透過 Symbol.prototype.toString() 間接取得:

const symbol = Symbol('foo');
// ^^^^^
symbol.toString();
// → 'Symbol(foo)'
// ^^^
symbol.toString().slice(7, -1); // 🤔
// → 'foo'

然而,這段程式碼看起來有點神秘,不太容易理解,並且違反了“表達意圖,而不是實作”的原則。上述技術也無法區分沒有描述的 Symbol (即 Symbol())和描述為空字串的 Symbol(即 Symbol(''))。

`Object.fromEntries`

· 閱讀時間約 4 分鐘
Mathias Bynens ([@mathias](https://twitter.com/mathias)), JavaScript whisperer

Object.fromEntries 是 JavaScript 內建函式庫的一個實用新增功能。在解釋它的功能之前,了解現有的 Object.entries API 會有所幫助。

Object.entries

Object.entries API 已經存在一段時間了。

對於物件中的每個鍵值對,Object.entries 會返回一個陣列,第一個元素是鍵,第二個元素是值。

Object.entries 尤其在與 for-of 結合使用時非常有用,因為它能讓你非常優雅地遍歷物件中的所有鍵值對:

const object = { x: 42, y: 50 };
const entries = Object.entries(object);
// → [['x', 42], ['y', 50]]

for (const [key, value] of entries) {
console.log(`The value of ${key} is ${value}.`);
}
// 日誌:
// x 的值是 42。
// y 的值是 50。

不幸的是,直到現在,還沒有一個簡單的方法可以將 entries 結果轉回到等效的物件。

Object.fromEntries

新的 Object.fromEntries API 執行了 Object.entries 的相反操作。這使得根據其 entries 重建物件變得簡單:

const object = { x: 42, y: 50 };
const entries = Object.entries(object);
// → [['x', 42], ['y', 50]]

const result = Object.fromEntries(entries);
// → { x: 42, y: 50 }

一個常見的用例是轉換物件。現在你可以通過遍歷它的 entries,然後使用你可能已經熟悉的陣列方法來完成:

const object = { x: 42, y: 50, abc: 9001 };
const result = Object.fromEntries(
Object.entries(object)
.filter(([ key, value ]) => key.length === 1)
.map(([ key, value ]) => [ key, value * 2 ])
);
// → { x: 84, y: 100 }

在這個例子中,我們使用 filter 過濾物件來僅保留鍵長度為 1 的鍵,也就是僅保留鍵 xy,不包括鍵 abc。接著,我們用 map 遍歷剩下的 entries,並為每個返回更新的鍵值對。此例中,我們通過將值乘以 2 來使每個值加倍。最終結果是一個新物件,僅包含屬性 xy 及其新值。

Promise 組合子

· 閱讀時間約 4 分鐘
Mathias Bynens ([@mathias](https://twitter.com/mathias))

自從在 ES2015 引入 Promise 以來,JavaScript 就支持了兩種 Promise 組合子:靜態方法 Promise.allPromise.race

目前有兩個新的提案正在標準化過程中:Promise.allSettledPromise.any。隨著這些新增內容,JavaScript 共有四種 Promise 組合子,每一種都支持不同的使用場景。

`Array.prototype.flat` 和 `Array.prototype.flatMap`

· 閱讀時間約 2 分鐘
Mathias Bynens ([@mathias](https://twitter.com/mathias))

Array.prototype.flat

此範例中的陣列是多層嵌套的:它包含一個陣列,而這個陣列又包含另一個陣列。

const array = [1, [2, [3]]];
// ^^^^^^^^^^^^^ 外層陣列
// ^^^^^^^^ 內層陣列
// ^^^ 最內層陣列

Array#flat 回傳一個展平後的陣列。

array.flat();
// → [1, 2, [3]]

// …等同於:
array.flat(1);
// → [1, 2, [3]]

預設的展平深度是 1,但您可以傳入任何數字值來遞迴展平到該深度。若要持續展平直到結果不再包含嵌套陣列,可以使用 Infinity

// 持續遞迴展平直到陣列不再包含嵌套陣列:
array.flat(Infinity);
// → [1, 2, 3]

這個方法為什麼叫做 Array.prototype.flat 而不是 Array.prototype.flatten 呢?閱讀我們的 #SmooshGate 撰寫內容來了解!

Array.prototype.flatMap

以下是另一個範例。我們有一個 duplicate 函數,它接受一個值並回傳一個包含該值兩次的陣列。如果我們將 duplicate 套用到陣列中的每個值,我們會得到一個嵌套陣列。

const duplicate = (x) => [x, x];

[2, 3, 4].map(duplicate);
// → [[2, 2], [3, 3], [4, 4]]

接著您可以對結果呼叫 flat 來展平陣列:

[2, 3, 4].map(duplicate).flat(); // 🐌
// → [2, 2, 3, 3, 4, 4]

由於此模式在函數式編程中相當常見,因此現在有一個專屬的 flatMap 方法。

[2, 3, 4].flatMap(duplicate); // 🚀
// → [2, 2, 3, 3, 4, 4]

flatMap 比起分別執行 mapflat 更加高效。

flatMap 的使用案例感興趣嗎?請查看 Axel Rauschmayer 的解釋

Array#{flat,flatMap} 支援

數字分隔符

· 閱讀時間約 2 分鐘
Mathias Bynens ([@mathias](https://twitter.com/mathias))

大型數字字面值難以讓人眼快速解析,尤其是當數字中有許多重複的數字時:

1000000000000
1019436871.42

為了提升可讀性,一項新的 JavaScript 語言特性允許在數字字面值中使用底線作為分隔符。因此,可以將上述數字重新編寫,以每千位分組為例:

`String.prototype.matchAll`

· 閱讀時間約 3 分鐘
Mathias Bynens ([@mathias](https://twitter.com/mathias))

在字串中重複套用同一個正則表達式以獲取所有匹配的情況並不罕見。某種程度上,這已經可以通過使用 String#match 方法實現。

在這個例子中,我們找到所有僅由十六進位數字組成的字,然後記錄每個匹配項:

const string = 'Magic hex numbers: DEADBEEF CAFE';
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
for (const match of string.match(regex)) {
console.log(match);
}

// 輸出:
//
// 'DEADBEEF'
// 'CAFE'

然而,這只會給你匹配的 子字串。通常,你不僅想要子字串,還希望獲取附加資訊,如每個子字串的索引,或者每次匹配時的捕捉群組。

這可以通過撰寫自己的迴圈並手動跟蹤匹配物件來實現,但這有點麻煩且不太方便:

const string = 'Magic hex numbers: DEADBEEF CAFE';
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
let match;
while (match = regex.exec(string)) {
console.log(match);
}

// 輸出:
//
// [ 'DEADBEEF', index: 19, input: 'Magic hex numbers: DEADBEEF CAFE' ]
// [ 'CAFE', index: 28, input: 'Magic hex numbers: DEADBEEF CAFE' ]

新的 String#matchAll API 使這變得前所未有的簡單:現在你可以撰寫一個簡單的 for-of 迴圈以獲取所有匹配物件。

const string = 'Magic hex numbers: DEADBEEF CAFE';
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
for (const match of string.matchAll(regex)) {
console.log(match);
}

// 輸出:
//
// [ 'DEADBEEF', index: 19, input: 'Magic hex numbers: DEADBEEF CAFE' ]
// [ 'CAFE', index: 28, input: 'Magic hex numbers: DEADBEEF CAFE' ]

String#matchAll 對於具有捕捉群組的正則表達式特別有用。它會提供每個匹配的完整資訊,包括捕捉群組。

const string = 'Favorite GitHub repos: tc39/ecma262 v8/v8.dev';
const regex = /\b(?<owner>[a-z0-9]+)\/(?<repo>[a-z0-9\.]+)\b/g;
for (const match of string.matchAll(regex)) {
console.log(`${match[0]} at ${match.index} with '${match.input}'`);
console.log(`→ owner: ${match.groups.owner}`);
console.log(`→ repo: ${match.groups.repo}`);
}

模組命名空間導出

· 閱讀時間約 1 分鐘
Mathias Bynens ([@mathias](https://twitter.com/mathias))

JavaScript 模組 中,先前已經可以使用以下語法:

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

然而,並沒有對稱的 export 語法……直到現在

export * as utils from './utils.mjs';

這相當於以下:

import * as utils from './utils.mjs';
export { utils };

公開與私有類別字段

· 閱讀時間約 4 分鐘
Mathias Bynens ([@mathias](https://twitter.com/mathias))

多個提案擴展了現有的 JavaScript 類別語法,提供了新功能。本文章解釋了 V8 v7.2 和 Chrome 72 中新增的公開類別字段語法,以及即將推出的私有類別字段語法。

以下是一個創建名為 IncreasingCounter 的類別實例的程式碼範例:

const counter = new IncreasingCounter();
counter.value;
// 記錄 '獲取目前的值!'
// → 0
counter.increment();
counter.value;
// 記錄 '獲取目前的值!'
// → 1

請注意,存取 value 會先執行某些程式碼(例如,記錄訊息)然後才返回結果。現在問問自己,您會如何在 JavaScript 中實作這個類別?🤔

ES2015 類別語法

以下是使用 ES2015 類別語法實作 IncreasingCounter 的方式:

class IncreasingCounter {
constructor() {
this._count = 0;
}
get value() {
console.log('獲取目前的值!');
return this._count;
}
increment() {
this._count++;
}
}

此類別在原型上安裝了 value 取得器和 increment 方法。更有趣的是,這個類別有一個建構子會創建 _count 實例屬性並將其預設值設定為 0。我們目前傾向使用底線前綴來表示 _count 不應直接被類別的用戶使用,但這只是一種慣例;它並不是語言強制的 真正 “私有”屬性。