ECMAScript 2024 正式發(fā)布,新特性一覽!
2024 年 6 月 26 日,第 127 屆 ECMA 大會正式批準了 ECMAScript 2024 語言規(guī)范,這意味著它現(xiàn)在正式成為最新 ECMAScript 標準。
圖片
下面就來看看 ECMAScript 2024 都有哪些新特性吧!
- Promise.withResolvers()
- Object.groupBy / Map.groupBy
- String:isWellFormed() / toWellFormed()
- ArrayBuffer:resize / transfer
- Atomics.waitAsync()
- 正則表達式 v 標志
Promise.withResolvers()
Promise.withResolvers() 允許創(chuàng)建一個新的 Promise,并同時獲得 resolve 和 reject 函數(shù)。
Promise.withResolvers() 等同于以下代碼,不過代碼會更簡潔:
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
通常,當創(chuàng)建一個新的 Promise 時,會傳遞一個執(zhí)行器函數(shù)給 Promise 構(gòu)造函數(shù),這個執(zhí)行器函數(shù)接收兩個參數(shù):resolve 和 reject 。但在某些情況下,可能想要在 Promise 創(chuàng)建之后仍然能夠訪問到這兩個函數(shù)。這就是 Promise.withResolvers() 的用武之地。
使用 Promise.withResolvers() 的例子:
const { promise, resolve, reject } = Promise.withResolvers();
// 在這里可以使用 resolve 和 reject 函數(shù)
setTimeout(() => resolve('成功!'), 8000);
promise.then(value => {
console.log(value); // 輸出: 成功!
});
使用 Promise.withResolvers() 關(guān)鍵的區(qū)別在于resolve 和 reject函數(shù)現(xiàn)在與 Promise 本身處于同一作用域,而不是在執(zhí)行器中被創(chuàng)建和一次性使用。這可能使得一些更高級的用例成為可能,例如在重復(fù)事件中重用它們,特別是在處理流和隊列時。這通常也意味著相比在執(zhí)行器內(nèi)包裝大量邏輯,嵌套會更少。這個方法對于那些需要更細粒度控制 Promise 的狀態(tài),或者在 Promise 創(chuàng)建后仍然需要訪問 resolve 和 reject 函數(shù)的場景來說非常有用。
瀏覽器支持:
圖片
Object.groupBy() / Map.groupBy()
Object.groupBy() 和 Map.groupBy() 方法用于數(shù)組分組。
假設(shè)有一個由表示水果的對象組成的數(shù)組,需要按照顏色進行分組。以前可以使用forEach循環(huán)來實現(xiàn),代碼如下:
const fruits = [
{ name: "Apple", color: "red" },
{ name: "Banana", color: "yellow" },
{ name: "Cherry", color: "red" },
{ name: "Lemon", color: "yellow" },
{ name: "Grape", color: "purple" },
];
const fruitsByColor = {};
fruits.forEach(fruit => {
const color = fruit.color;
if (!fruitsByColor[color]) {
fruitsByColor[color] = [];
}
fruitsByColor[color].push(fruit);
});
console.log(fruitsByColor);
輸出結(jié)果如下:
圖片
也可以使用reduce方法:
const fruitsByColor = fruits.reduce((acc, fruit) => {
const color = fruit.color;
if (!acc[color]) {
acc[color] = [];
}
acc[color].push(fruit);
return acc;
}, {});
無論哪種方式,代碼都略顯繁瑣。每次都要檢查對象,看分組的 key 是否存在,如果不存在,則創(chuàng)建一個空數(shù)組,并將項目添加到該數(shù)組中。
Object.groupBy()
可以通過以下方式來使用新的Object.groupBy方法,代碼更簡潔:
const fruitsByColor = Object.groupBy(fruits, (fruit) => fruit.color);
需要注意,使用Object.groupBy方法返回一個沒有原型(即沒有繼承任何屬性和方法)的對象。這意味著該對象不會繼承Object.prototype上的任何屬性或方法,例如hasOwnProperty或toString等。雖然這樣做可以避免意外覆蓋Object.prototype上的屬性,但也意味著不能使用一些與對象相關(guān)的方法。
const fruitsByColor = Object.groupBy(fruits, (fruit) => fruit.color);
console.log(fruitsByColor.hasOwnProperty("red"));
// TypeError: fruitsByColor.hasOwnProperty is not a function
在調(diào)用Object.groupBy時,傳遞給它的回調(diào)函數(shù)應(yīng)該返回一個字符串或 Symbol 類型的值。如果回調(diào)函數(shù)返回其他類型的值,它將被強制轉(zhuǎn)換為字符串。
瀏覽器支持:
圖片
Map.groupBy()
Map.groupBy和Object.groupBy的功能一樣,只是返回的結(jié)果類型不同。Map.groupBy返回一個 Map 對象,而Object.groupBy返回一個普通對象。
const fruits = [
{ name: "Apple", color: "red" },
{ name: "Banana", color: "yellow" },
{ name: "Cherry", color: "red" },
{ name: "Lemon", color: "yellow" },
{ name: "Grape", color: "purple" },
];
const fruitsByColor = Map.groupBy(fruits, (fruit) => fruits.color);
這里根據(jù)水果顏色進行了分組,輸出結(jié)果如下:
圖片
可以通過 Map 的 get 方法來來獲取分組分組結(jié)果:
fruitsByColor.get("red");
// [{"name": "Apple", "color": "red"}, {"name": "Cherry", "color": "red"}]
瀏覽器支持:
String:isWellFormed() / toWellFormed()
String.prototype.isWellFormed()
isWellFormed() 用于檢查一個 UTF-16 編碼的字符串是否包含孤立的代理項(即未與另一個代理項配對的高代理或低代理)。UTF-16使用代理對來表示Unicode中超過基本多文種平面的字符。一個有效的UTF-16字符串應(yīng)該只包含正確配對的代理對或單獨的BMP字符。
下面來看一個例子
const strings = [
// 單獨的前導(dǎo)代理
"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
如果傳遞的字符串格式不正確, encodeURI 會拋出錯誤??梢酝ㄟ^使用 isWellFormed() 在將字符串傳遞給 encodeURI() 之前測試字符串來避免這種情況。
const illFormed = "https://example.com/search?q=\uD800";
try {
encodeURI(illFormed);
} catch (e) {
console.log(e); // URIError: URI malformed
}
if (illFormed.isWellFormed()) {
console.log(encodeURI(illFormed));
} else {
console.warn("Ill-formed strings encountered."); // Ill-formed strings encountered.
}
isWellFormed() 函數(shù)的使用場景主要包括以下幾種情況:
- 數(shù)據(jù)驗證:當你從外部源(如用戶輸入、文件、網(wǎng)絡(luò)請求等)接收字符串時,你可能想要驗證這些字符串是否包含有效的UTF-16編碼。如果字符串包含孤立的代理項(即沒有配對的高代理或低代理),那么它可能不是有效的UTF-16字符串,這可能會導(dǎo)致后續(xù)處理時出錯。
- 文本處理:在處理文本數(shù)據(jù)(如搜索、排序、轉(zhuǎn)換等)時,確保文本是有效編碼的非常重要。如果文本包含錯誤的編碼,那么處理結(jié)果可能會是不正確的或不可預(yù)測的。
- 網(wǎng)絡(luò)傳輸:當你通過網(wǎng)絡(luò)發(fā)送或接收文本數(shù)據(jù)時,確保數(shù)據(jù)的編碼是正確的至關(guān)重要。錯誤的編碼可能導(dǎo)致數(shù)據(jù)在傳輸過程中被損壞,或者在接收端無法正確解析。
- 數(shù)據(jù)庫存儲:在將文本數(shù)據(jù)存儲到數(shù)據(jù)庫之前,驗證其編碼的正確性也是一個好習(xí)慣。這可以確保數(shù)據(jù)的完整性和可讀性,并避免在后續(xù)查詢或處理時出現(xiàn)問題。
- 用戶界面顯示:在用戶界面中顯示文本時,確保文本是有效編碼的也很重要。錯誤的編碼可能導(dǎo)致文本無法正確顯示,或者顯示出不正確的字符。
瀏覽器支持:
圖片
String.prototype.toWellFormed()
toWellFormed() 方法返回一個字符串,其中該字符串的所有單獨代理項都被替換為 Unicode 替換字符 U+FFFD。
toWellFormed() 迭代字符串的碼元,并將任何單獨代理項替換為 Unicode 替換字符 U+FFFD。這確保了返回的字符串格式正確并可用于期望正確格式字符串的函數(shù),比如 encodeURI。由于引擎能夠直接訪問字符串的內(nèi)部表示,與自定義實現(xiàn)相比 toWellFormed() 更高效。
const strings = [
// 單獨的前導(dǎo)代理
"ab\uD800",
"ab\uD800c",
// 單獨的后尾代理
"\uDFFFab",
"c\uDFFFab",
// 格式正確
"abc",
"ab\uD83D\uDE04c",
];
for (const str of strings) {
console.log(str.toWellFormed());
}
// 輸出:
// "ab?"
// "ab?c"
// "?ab"
// "c?ab"
// "abc"
// "ab??c"
如果傳遞的字符串格式不正確, encodeURI 會拋出錯誤??梢韵韧ㄟ^使用 toWellFormed() 將字符串轉(zhuǎn)換為格式正確的字符串來避免這種情況。
const illFormed = "https://example.com/search?q=\uD800";
try {
encodeURI(illFormed);
} catch (e) {
console.log(e); // URIError: URI malformed
}
console.log(encodeURI(illFormed.toWellFormed())); // "https://example.com/search?q=%EF%BF%BD"
瀏覽器支持:
圖片
ArrayBuffer:resize / transfer
ArrayBuffer.prototype.resize
ArrayBuffer 實例的 resize() 方法將 ArrayBuffer 調(diào)整為指定的大小,以字節(jié)為單位,前提是該 ArrayBuffer 是可調(diào)整大小的并且新的大小小于或等于該 ArrayBuffer 的 maxByteLength。
const buffer = new ArrayBuffer(8, { maxByteLength: 16 });
console.log(buffer.byteLength); // 8
if (buffer.resizable) {
console.log("緩沖區(qū)大小是可調(diào)整的!");
buffer.resize(12);
}
注意:
- 如果 ArrayBuffer 已分離或不可調(diào)整大小,則拋出該錯誤。
- 如果 newLength 大于該 ArrayBuffer 的 maxByteLength,則拋出該錯誤。
瀏覽器支持:
ArrayBuffer.prototype.transfer
transfer() 方法執(zhí)行與結(jié)構(gòu)化克隆算法相同的操作。它將當前 ArrayBuffer 的字節(jié)復(fù)制到一個新的 ArrayBuffer 對象中,然后分離當前 ArrayBuffer 對象,保留了當前 ArrayBuffer 的大小可調(diào)整性。
// 創(chuàng)建一個 ArrayBuffer 并寫入一些字節(jié)
const buffer = new ArrayBuffer(8);
const view = new Uint8Array(buffer);
view[1] = 2;
view[7] = 4;
// 將緩沖區(qū)復(fù)制到另一個相同大小的緩沖區(qū)
const buffer2 = buffer.transfer();
console.log(buffer.detached); // true
console.log(buffer2.byteLength); // 8
const view2 = new Uint8Array(buffer2);
console.log(view2[1]); // 2
console.log(view2[7]); // 4
// 將緩沖區(qū)復(fù)制到一個更小的緩沖區(qū)
const buffer3 = buffer2.transfer(4);
console.log(buffer3.byteLength); // 4
const view3 = new Uint8Array(buffer3);
console.log(view3[1]); // 2
console.log(view3[7]); // undefined
// 將緩沖區(qū)復(fù)制到一個更大的緩沖區(qū)
const buffer4 = buffer3.transfer(8);
console.log(buffer4.byteLength); // 8
const view4 = new Uint8Array(buffer4);
console.log(view4[1]); // 2
console.log(view4[7]); // 0
// 已經(jīng)分離,拋出 TypeError
buffer.transfer(); // TypeError: Cannot perform ArrayBuffer.prototype.transfer on a detached ArrayBuffer
瀏覽器支持:
圖片
Atomics.waitAsync()
Atomics.waitAsync() 靜態(tài)方法異步等待共享內(nèi)存的特定位置并返回一個 Promise。與 Atomics.wait() 不同,waitAsync 是非阻塞的且可用于主線程。
下面來看一個簡單的例子,給定一個共享的 Int32Array。
const sab = new SharedArrayBuffer(1024);
const int32 = new Int32Array(sab);
令一個讀取線程休眠并在位置 0 處等待,預(yù)期該位置的值為 0。result.value 將是一個 promise。
const result = Atomics.waitAsync(int32, 0, 0, 1000);
// { async: true, value: Promise {<pending>} }
在該讀取線程或另一個線程中,對內(nèi)存位置 0 調(diào)用以令該 promise 解決為 "ok"。
Atomics.notify(int32, 0);
// { async: true, value: Promise {<fulfilled>: 'ok'} }
如果它沒有解決為 "ok",則共享內(nèi)存該位置的值不符合預(yù)期(value 將是 "not-equal" 而不是一個 promise)或已經(jīng)超時(該 promise 將解決為 "time-out")。
瀏覽器支持:
圖片
正則表達式 v 標志
RegExp 的 v 標志是 u 標志的超集,并提供了另外兩個功能:
- 字符串的 Unicode 屬性:通過 Unicode 屬性轉(zhuǎn)義,可以使用字符串的屬性。
const re = /^\p{RGI_Emoji}$/v;
// 匹配僅包含 1 個代碼點的表情符號:
re.test('?'); // '\u26BD' // true
// 匹配由多個代碼點組成的表情符號:
re.test('??????'); // '\u{1F468}\u{1F3FE}\u200D\u2695\uFE0F' // true
- 設(shè)置符號:允許在字符類之間進行集合操作。
const re = /[\p{White_Space}&&\p{ASCII}]/v;
re.test('\n'); // true
re.test('\u2028'); // false
那 u 標志是干什么的呢?
在 JavaScript 中,RegExp 對象的 u 標志(也稱為 Unicode 標志)是用于處理 Unicode 字符的。當在正則表達式字面量或構(gòu)造函數(shù)中使用 u 標志時,它將改變正則表達式的行為,以便更準確地處理 Unicode 字符。以下是 u 標志的主要作用:
- 完整 Unicode 字符匹配: 在 Unicode 中,有些字符是由多個“代碼單元”(在 UTF-16 編碼中)組成的,如表情符號或某些特殊字符。不使用 u 標志時,正則表達式可能會將這些字符拆分成多個代碼單元,導(dǎo)致不正確的匹配。使用 u 標志后,正則表達式會將整個字符視為一個單位,從而進行更準確的匹配。
- Unicode 屬性的支持: 使用 u 標志的正則表達式可以訪問 Unicode 字符的屬性,如是否為大寫字母、是否為標點符號等。這可以通過在正則表達式中使用 \p{...} 和 \P{...} 序列來實現(xiàn),其中 {...} 中是 Unicode 屬性的名稱。
- Unicode 字符類別的支持: 與 Unicode 屬性類似,使用 u 標志的正則表達式還可以支持 Unicode 字符類別,如 \p{Letter} 匹配任何字母字符,\p{Number} 匹配任何數(shù)字字符等。
- 點號(.)的新行為: 在默認情況下,正則表達式中的點號(.)不匹配換行符,但會匹配任何單個字符(在 UTF-16 編碼中是一個代碼單元)。但是,當使用 u 標志時,點號將匹配任何單個 Unicode 字符,包括那些由多個 UTF-16 代碼單元組成的字符。
- 對量詞和邊界斷言的改進: 使用 u 標志的正則表達式將改進對 Unicode 字符的量詞(如 *, +, ?, {n}, {n,}, {n,m})和邊界斷言(如 ^ 和 $)的處理。這確保了它們在整個 Unicode 字符上正確工作,而不是僅僅在 UTF-16 代碼單元上。
- 更準確的字符類: 使用 u 標志的正則表達式將更準確地解釋字符類,如 [a-z]。在不使用 u 標志時,這個字符類可能只匹配基本的拉丁字母。但是,使用 u 標志后,它將根據(jù) Unicode 標準來匹配任何被視為“小寫字母”的字符,包括來自其他語言的小寫字母。
下面是一個使用 u 標志的例子:
const regex = /^\p{Letter}+$/u;
console.log(regex.test('你好')); // 輸出:true
console.log(regex.test('123')); // 輸出:false
在這個例子中,正則表達式 \p{Letter}+ 匹配一個或多個 Unicode 字母字符。由于 '你好' 包含兩個 Unicode 字母字符(即使它們在 UTF-16 編碼中由多個代碼單元組成),所以 regex.test('你好') 返回 true。而 '123' 不包含任何 Unicode 字母字符,所以 regex.test('123') 返回 false。