`Intl.NumberFormat`
你可能已經熟悉 Intl.NumberFormat
API,因為它在現代環境中已被支援了一段時間。
在其最基本的形式中,Intl.NumberFormat
讓您可以建立一個可重用的格式器實例,支援語系感知的數字格式化。就像其他 Intl.*Format
API 一樣,格式器實例同時支持 format
與 formatToParts
方法:
你可能已經熟悉 Intl.NumberFormat
API,因為它在現代環境中已被支援了一段時間。
在其最基本的形式中,Intl.NumberFormat
讓您可以建立一個可重用的格式器實例,支援語系感知的數字格式化。就像其他 Intl.*Format
API 一樣,格式器實例同時支持 format
與 formatToParts
方法:
如果你曾經為瀏覽器編寫 JavaScript,可能使用過 window
來訪問全域 this
。在 Node.js 中,你可能使用過 global
。如果你編寫了需要同時在這兩個環境中運作的代碼,可能會檢測哪個可用,然後使用它 —— 但隨著你要支持的環境和使用情境增加,需要檢查的標識符列表也會越來越長,事情很快就會變得難以控制:
通常,在 JavaScript 中對物件的引用是 強引用,這意味著只要你擁有對該物件的引用,它就不會被垃圾回收機制回收。
const ref = { x: 42, y: 51 };
// 只要你能訪問 `ref`(或者其他任何對同一物件的引用),該物件就不會被垃圾回收機制回收。
目前,WeakMap
和 WeakSet
是 JavaScript 中唯一可以弱引用物件的方式:將物件作為 WeakMap
或 WeakSet
的鍵添加,並不會防止該物件被垃圾回收機制回收。
const wm = new WeakMap();
{
const ref = {};
const metaData = 'foo';
wm.set(ref, metaData);
wm.get(ref);
// → metaData
}
// 在這個區塊範圍內,我們不再有對 `ref` 的引用,因此它
// 現在可以被垃圾回收機制回收,儘管它是 `wm` 的一個鍵
// 且我們仍然可以訪問 `wm`。
假設你有一個狗狗的陣列,每隻狗有一個名字和一個評級。(如果這聽起來是個奇怪的例子,你應該知道,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);
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
的鍵,也就是僅保留鍵 x
和 y
,不包括鍵 abc
。接著,我們用 map
遍歷剩下的 entries,並為每個返回更新的鍵值對。此例中,我們通過將值乘以 2
來使每個值加倍。最終結果是一個新物件,僅包含屬性 x
和 y
及其新值。
自從在 ES2015 引入 Promise 以來,JavaScript 就支持了兩種 Promise 組合子:靜態方法 Promise.all
和 Promise.race
。
目前有兩個新的提案正在標準化過程中:Promise.allSettled
和 Promise.any
。隨著這些新增內容,JavaScript 共有四種 Promise 組合子,每一種都支持不同的使用場景。
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
比起分別執行 map
和 flat
更加高效。
對 flatMap
的使用案例感興趣嗎?請查看 Axel Rauschmayer 的解釋。
Array#{flat,flatMap}
支援大型數字字面值難以讓人眼快速解析,尤其是當數字中有許多重複的數字時:
1000000000000
1019436871.42
為了提升可讀性,一項新的 JavaScript 語言特性允許在數字字面值中使用底線作為分隔符。因此,可以將上述數字重新編寫,以每千位分組為例:
在字串中重複套用同一個正則表達式以獲取所有匹配的情況並不罕見。某種程度上,這已經可以通過使用 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}`);
}
現代的網絡應用通常使用包含動態數據的列表。例如,一個相片查看應用可能顯示如下內容:
此相片包括 Ada、Edith、和 Grace。
一款文字遊戲可能會顯示另一種類型的列表:
選擇你的超能力:隱形、心靈控制、或 共情能力。
由於每種語言的列表格式化習慣和詞語各不相同,實現一個本地化的列表格式化器並非易事。不僅需要獲取所有希望支持語言中的相關詞語(如上例中的 “and” 或 “or”),還需要對所有這些語言的格式化習慣進行編碼!Unicode CLDR 提供這些數據,但要在 JavaScript 中使用它,這些數據需嵌入並隨其他庫代碼一起傳遞。這樣不幸會增加庫的捆綁大小,進而對加載時間、解析/編譯成本及內存消耗造成負面影響。