`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}.`);
}
// 输出:
// The value of x is 42.
// The value of y is 50.
遗憾的是,想从 entries 的结果回到等价对象并不容易……直到现在!
Object.fromEntries
新的 Object.fromEntries
API 执行了 Object.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 }
一个常见的用例是转换对象。现在可以通过迭代其条目,然后使用你已经熟悉的数组方法来做到这一点:
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
对剩余的条目进行操作,并为每个条目返回一个更新后的键值对。在本例中,我们通过将值乘以 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]]
大型数字字面量对人眼来说难以快速解析,特别是当数字中有大量重复数字时:
1000000000000
1019436871.42
为了提高可读性,一个新的JavaScript语言特性允许在数字字面量中使用下划线作为分隔符。因此,上述内容现在可以改写为按千分进行分组,例如:
通常会在字符串上重复应用相同的正则表达式以获取所有匹配项。在一定程度上,现在可以通过使用 String#match
方法来实现这一点。
在这个例子中,我们找到所有仅包含十六进制数字的单词,并记录每个匹配项:
const string = '魔术十六进制数字: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 = '魔术十六进制数字: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: '魔术十六进制数字:DEADBEEF CAFE' ]
// [ 'CAFE', index: 28, input: '魔术十六进制数字:DEADBEEF CAFE' ]
新的 String#matchAll
API 使得这一过程比以往更加简单:你现在可以编写一个简单的 for
-of
循环来获取所有的匹配对象。
const string = '魔术十六进制数字: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: '魔术十六进制数字:DEADBEEF CAFE' ]
// [ 'CAFE', index: 28, input: '魔术十六进制数字:DEADBEEF CAFE' ]
String#matchAll
对于带有捕获组的正则表达式特别有用。它为每个匹配提供完整的信息,包括捕获组。
const string = '喜欢的 GitHub 仓库: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]} 在 ${match.index} 上,输入为 '${match.input}'`);
console.log(`→ 所有者: ${match.groups.owner}`);
console.log(`→ 仓库: ${match.groups.repo}`);
}
现代的网页应用通常使用包含动态数据的列表。例如,一个照片查看器应用可能会显示如下内容:
此照片包含了 Ada、Edith 和 Grace。
而一个基于文本的游戏可能需要展示不同形式的列表:
选择你的超级能力: 隐身、心灵致动 或 共情。
由于每种语言具有不同的列表格式化惯例和用词,实现一个本地化列表格式化工具是极其复杂的。这不仅需要列出每种语言中所有的单词(如上述例子中的 “和” 或 “或”),还需要在编码时定义这些语言的具体格式化规则。Unicode CLDR 提供了这些数据,但在 JavaScript 中使用这些数据需要事先嵌入,并与其他库代码一起发送。这不幸会增加这些库的打包体积,从而对加载时间、解析/编译成本以及内存消耗产生负面影响。