ECMAScript 2024 新特性
ECMAScript
2024,第 15 版,添加了用于调整 ArrayBuffer
和 SharedArrayBuffer
大小和传输的功能; 添加了一个新的 RegExp /v
标志,用于创建具有更高级功能的 RegExp,用于处理字符串集; 并介绍了用于构造 Promise
的 Promise.withResolvers
便捷方法、用于聚合数据的 Object.groupBy
和 Map.groupBy
方法、用于异步等待共享内存更改的 Atomics.waitAsync
方法以及 String.prototype.isWellFormed
和 String.prototype.toWellFormed
方法,用于检查并确保字符串仅包含格式正确的 Unicode
。
一、Promise.withResolvers ( )
该函数返回一个具有三个属性的对象:一个新的 Promise
以及与其关联的解决和拒绝函数。
1. 返回值
包含以下属性的普通对象:
1.1. promise
一个 Promise
对象。
1.2. resolve
一个函数,用于解决该 Promise
。
1.3. reject
一个函数,用于拒绝该 Promise
。
2. 示例
Promise.withResolvers()
的使用场景是,当你有一个 promise
,需要通过无法包装在 promise
执行器内的某个事件监听器来解决或拒绝。
async function* readableToAsyncIterable(stream) { let { promise, resolve, reject } = Promise.withResolvers(); stream.on("error", (error) => reject(error)); stream.on("end", () => resolve()); stream.on("readable", () => resolve()); while (stream.readable) { await promise; let chunk; while ((chunk = stream.read())) { yield chunk; } ({ promise, resolve, reject } = Promise.withResolvers()); } }
3. 等价于
Promise.withResolvers()
完全等同于以下代码:
let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
使用 Promise.withResolvers()
关键的区别在于解决和拒绝函数现在与 Promise
本身处于同一作用域,而不是在执行器中被创建和一次性使用。
4. 在非 Promise 构造函数上调用 withResolvers()
Promise.withResolvers()
是一个通用方法。它可以在任何实现了与 Promise()
构造函数相同签名的构造函数上调用。
例如,我们可以在一个将 console.log
作为 resolve
和 reject
函数传入给 executor
的构造函数上调用它:
class NotPromise { constructor(executor) { // “resolve”和“reject”函数和原生的 promise 的行为完全不同 // 但 Promise.withResolvers() 只是返回它们,就像是原生的 promise 一样 executor( (value) => console.log("以", value, "解决"), (reason) => console.log("以", reason, "拒绝"), ); } } const { promise, resolve, reject } = Promise.withResolvers.call(NotPromise); resolve("hello");
二、Object.groupBy ( items, callbackfn )
callbackfn
是一个接受两个参数的函数。 groupBy
对 items
中的每个元素按升序调用一次 callbackfn
,并构造一个新对象。 Callbackfn
返回的每个值都被强制转换为属性键。 对于每个这样的属性键,结果对象都有一个属性,其键是该属性键,其值是一个数组,其中包含回调函数返回值强制为该键的所有元素。
使用两个参数调用 callbackfn
:元素的值和元素的索引。
groupBy
的返回值是一个不继承自 Object.prototype
的对象。
1. 作用
Object.groupBy() 静态方法根据提供的回调函数返回的字符串值对给定可迭代对象中的元素进行分组。返回的对象具有每个组的单独属性,其中包含组中的元素的数组。
2. 参数
2.1. items
一个将进行元素分组的可迭代对象(例如 Array
)。
2.2. callbackFn
对可迭代对象中的每个元素执行的函数。它应该返回一个值,可以被强制转换成属性键(字符串或 symbol
),用于指示当前元素所属的分组。该函数被调用时将传入以下参数:
element
:数组中当前正在处理的元素。index
:正在处理的元素在数组中的索引。
3. 返回值
一个带有所有分组属性的 null
原型对象,每个属性都分配了一个包含相关组元素的数组。
4. 示例
4.1. 根据 element 元素分组
Object.groupBy([ { name: "芦笋", type: "蔬菜", quantity: 5 }, { name: "香蕉", type: "水果", quantity: 0 }, { name: "山羊", type: "肉", quantity: 23 }, { name: "樱桃", type: "水果", quantity: 5 }, { name: "鱼", type: "肉", quantity: 22 }, ], ({ type }) => type) // 输出 /** { "蔬菜": [ { "name": "芦笋", "type": "蔬菜", "quantity": 5 } ], "水果": [ { "name": "香蕉", "type": "水果", "quantity": 0 }, { "name": "樱桃", "type": "水果", "quantity": 5 } ], "肉": [ { "name": "山羊", "type": "肉", "quantity": 23 }, { "name": "鱼", "type": "肉", "quantity": 22 } ] } */
4.2. 自定义分组
const myCallback = ({ quantity }) => { return quantity > 5 ? "ok" : "restock"; } const result = Object.groupBy([ { name: "芦笋", type: "蔬菜", quantity: 5 }, { name: "香蕉", type: "水果", quantity: 0 }, { name: "山羊", type: "肉", quantity: 23 }, { name: "樱桃", type: "水果", quantity: 5 }, { name: "鱼", type: "肉", quantity: 22 }, ], myCallback); // 输出 /** { "restock": [ { "name": "芦笋", "type": "蔬菜", "quantity": 5 }, { "name": "香蕉", "type": "水果", "quantity": 0 }, { "name": "樱桃", "type": "水果", "quantity": 5 } ], "ok": [ { "name": "山羊", "type": "肉", "quantity": 23 }, { "name": "鱼", "type": "肉", "quantity": 22 } ] } */
三、Map.groupBy ( items, callbackfn )
callbackfn
是一个接受两个参数的函数。 groupBy
对 items
中的每个元素按升序调用一次回调函数,并构造一个新的 Map
。 callbackfn
返回的每个值都用作 Map
中的键。 对于每个这样的键,结果 Map
都有一个条目,其键是该键,其值是一个数组,其中包含 callbackfn
返回该键的所有元素。
使用两个参数调用 callbackfn
:元素的值和元素的索引。
groupBy
的返回值是一个 Map
。
1. 作用
Map.groupBy() 静态方法使用提供的回调函数返回的值对给定可迭代对象中的元素进行分组。最终返回的 Map
使用测试函数返回的唯一值作为键,可用于获取每个组中的元素组成的数组。
2. 参数
2.1. items
一个将进行元素分组的可迭代对象(例如 Array
)。
2.2. callbackFn
对可迭代对象中的每个元素执行的函数。它应该返回一个值(对象或原始类型)来表示当前元素的分组。该函数被调用时将传入以下参数:
element
:数组中当前正在处理的元素。index
:正在处理的元素在数组中的索引。
3. 返回值
一个包含了每一个组的键的 Map
对象,每个键都分配了一个包含关联组元素的数组。
4. 示例
const restock = { restock: true }; const sufficient = { restock: false }; const result = Map.groupBy([ { name: "芦笋", type: "蔬菜", quantity: 9 }, { name: "香蕉", type: "水果", quantity: 5 }, { name: "山羊", type: "肉", quantity: 23 }, { name: "樱桃", type: "水果", quantity: 12 }, { name: "鱼", type: "肉", quantity: 22 }, ], ({ quantity }) => quantity < 6 ? restock : sufficient, ); // 输出 result Map /** new Map([ [ { "restock": false }, [ { "name": "芦笋", "type": "蔬菜", "quantity": 9 }, { "name": "山羊", "type": "肉", "quantity": 23 }, { "name": "樱桃", "type": "水果", "quantity": 12 }, { "name": "鱼", "type": "肉", "quantity": 22 } ] ], [ { "restock": true }, [ { "name": "香蕉", "type": "水果", "quantity": 5 } ] ] ]) */
四、Atomics.waitAsync ( typedArray, index, value, timeout )
此函数返回一个 Promise
,当通知调用代理或达到超时时,该 Promise
会被解析。
1. 作用
Atomics.waitAsync() 静态方法异步等待共享内存的特定位置并返回一个 Promise。
2. 参数
typedArray
:基于SharedArrayBuffer
的Int32Array
或BigInt64Array
。index
:typedArray
中要等待的位置。value
:要测试的期望值。timeout
:可选 等待时间,以毫秒为单位。NaN
(以及会被转换为NaN
的值,例如undefined
)会被转换为Infinity
。负值会被转换为 0。
3. 返回值
一个 Object
,包含以下属性:
async
:一个布尔值,指示value
属性是否为Promise
。value
:如果async
是false
,它将是一个内容为 "not-equal
" 或 "timed-out
" 的字符串(仅当timeout
参数为 0 时)。如果async
是true
,它将会是一个Promise
,其兑现值为一个内容为 "ok
" 或 "timed-out
" 的字符串。这个promise
永远不会被拒绝。
4. 异常
TypeError
:如果typedArray
不是一个基于SharedArrayBuffer
的Int32Array
或BigInt64Array
,则抛出该异常。RangeError
:如果index
超出typedArray
的范围,则抛出该异常。
5. 示例
给定一个共享的 Int32Array
。
const sab = new SharedArrayBuffer(1024); const int32 = new Int32Array(sab);
令一个读取线程休眠并在位置 0 处等待,预期该位置的值为 0。result.value
将是一个 promise
。
const result = Atomics.waitAsync(int32, 0, 0, 1000); // { async: true, value: Promise {<pending>} }
在该读取线程或另一个线程中,对内存位置 0 调用以令该 promise
为 "ok
"。
Atomics.notify(int32, 0); // { async: true, value: Promise {<fulfilled>: 'ok'} }
如果它没有为 "ok
",则共享内存该位置的值不符合预期(value
将是 "not-equal
" 而不是一个 promise
)或已经超时(该 promise
将为 "time-out
")。
五、String.prototype.isWellFormed ( )
1. 作用
isWellFormed() 方法返回一个表示该字符串是否包含单独代理项的布尔值。
1.1. 单独代理项
单独代理项(lone surrogate) 是指满足以下描述之一的 16 位码元:
- 它在范围 0xD800 到 0xDBFF 内(含)(即为前导代理),但它是字符串中的最后一个码元,或者下一个码元不是后尾代理。
- 它在范围 0xDC00 到 0xDFFF 内(含)(即为后尾代理),但它是字符串中的第一个码元,或者前一个码元不是前导代理。
2. 返回值
如果字符串不包含单独代理项,返回 true
,否则返回 false
。
3. 示例
const strings = [ // 单独的前导代理 "ab\uD800", "ab\uD800c", // 单独的后尾代理 "\uDFFFab", "c\uDFFFab", // 格式正确 "abc", "ab\uD83D\uDE04c", ]; for (const str of strings) { console.log(str.isWellFormed()); } // 输出: // false // false // false // false // true // true
六、String.prototype.toWellFormed ( )
1. 作用
toWellFormed() 方法返回一个字符串,其中该字符串的所有单独代理项都被替换为 Unicode
替换字符 U+FFFD
。
2. 返回值
新的字符串是原字符串的一个拷贝,其中所有的单独代理项被替换为 Unicode
替换字符 U+FFFD
。
3. 示例
const strings = [ // 单独的前导代理 "ab\uD800", "ab\uD800c", // 单独的后尾代理 "\uDFFFab", "c\uDFFFab", // 格式正确 "abc", "ab\uD83D\uDE04c", ]; for (const str of strings) { console.log(str.toWellFormed()); } // Logs: // "ab�" // "ab�c" // "�ab" // "c�ab" // "abc" // "ab😄c"
七、RegExp /v
1. 作用
/v
解锁了对扩展字符类的支持,包括以下功能:
2. 示例
2.1. 基础示例
const re = /…/v;
2.2. Unicode
const re = /^\p{RGI_Emoji}$/v; re.test('⚽'); // '\u26BD' // → true ✅ re.test('👨🏾⚕️'); // '\u{1F468}\u{1F3FE}\u200D\u2695\uFE0F' // → true ✅
v
标志支持字符串的以下 Unicode
属性:
Basic_Emoji
Emoji_Keycap_Sequence
RGI_Emoji_Modifier_Sequence
RGI_Emoji_Flag_Sequence
RGI_Emoji_Tag_Sequence
RGI_Emoji_ZWJ_Sequence
RGI_Emoji
随着 Unicode
标准定义了字符串的其他属性,受支持的属性列表将来可能会增加。
2.3. 结合 --
/[\p{Script_Extensions=Greek}--π]/v.test('π'); // → false /[\p{Script_Extensions=Greek}--[αβγ]]/v.test('α'); // → false /[\p{Script_Extensions=Greek}--[α-γ]]/v.test('β'); // → false /[\p{Decimal_Number}--[0-9]]/v.test('𑜹'); // → true /[\p{Decimal_Number}--[0-9]]/v.test('4'); // → false /^\p{RGI_Emoji_Tag_Sequence}$/v.test('🏴'); // → true /^[\p{RGI_Emoji_Tag_Sequence}--\q{🏴}]$/v.test('🏴'); // → false
2.4. 结合 &&
const re = /[\p{Script_Extensions=Greek}&&\p{Letter}]/v; re.test('π'); // → true re.test('𐆊'); // → false const re2 = /[\p{White_Space}&&\p{ASCII}]/v; re2.test('\n'); // → true re2.test('\u2028'); // → false
3. V 标志与 U 标志有何不同
- 使用新语法的无效模式现在变得有效
- 一些以前有效的模式现在是错误的,特别是那些字符类包含未转义特殊字符
( ) [ { } / - |
的模式或双标点符号 u
标志存在令人困惑的不区分大小写的匹配行为。v
标志具有不同的、改进的语义