2024 年 6 月 26 日,第 127 屆 ECMA 大會正式批準了 ECMAScript 2024 語言規範,這意味著它現在正式成為最新 ECMAScript 標準。
下面就來看看 ECMAScript 2024 都有哪些新特性吧!
Promise.withResolvers()
Object.groupBy
/
Map.groupBy
String:
isWellFormed()
/
toWellFormed()
ArrayBuffer:
resize
/
transfer
Atomics.waitAsync()
正規表式
v
標誌
Promise.withResolvers()
Promise.withResolvers()
允許建立一個新的
Promise
,並同時獲得
resolve
和
reject
函式。
Promise.withResolvers()
等同於以下程式碼,不過程式碼會更簡潔:
let resolve, reject;
const promise =newPromise((res, rej)=>{
resolve = res;
reject = rej;
});
通常,當建立一個新的 Promise 時,會傳遞一個執行器函式給
Promise
建構函式,這個執行器函式接收兩個參數:
resolve
和
reject
。但在某些情況下,可能想要在
Promise
建立之後仍然能夠存取到這兩個函式。這就是
Promise.withResolvers()
的用武之地。
使用
Promise.withResolvers()
的例子:
const{ promise, resolve, reject }=Promise.withResolvers();
// 在這裏可以使用 resolve 和 reject 函式
setTimeout(()=>resolve('成功!'),8000);
promise.then(value =>{
console.log(value);// 輸出: 成功!
});
使用
Promise.withResolvers()
關鍵的區別在於
resolve
和
reject
函式現在與 Promise 本身處於同一作用域,而不是在執行器中被建立和一次性使用。這可能使得一些更高級的用例成為可能,例如在重復事件中重用它們,特別是在處理流和佇列時。這通常也意味著相比在執行器內包裝大量邏輯,巢狀會更少。這個方法對於那些需要更細粒度控制
Promise
的狀態,或者在
Promise
建立後仍然需要存取
resolve
和
reject
函式的場景來說非常有用。
瀏覽器支持:
Object.groupBy() / Map.groupBy()
Object.groupBy()
和
Map.groupBy()
方法用於陣列分組。
假設有一個由表示水果的物件組成的陣列,需要按照
顏色
進行分組。以前可以使用
forEach
迴圈來實作,程式碼如下:
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);
輸出結果如下:
也可以使用
reduce
方法:
const fruitsByColor = fruits.reduce((acc, fruit)=>{
const color = fruit.color;
if(!acc[color]){
acc[color]=[];
}
acc[color].push(fruit);
return acc;
},{});
無論哪種方式,程式碼都略顯繁瑣。每次都要檢查物件,看分組的
key
是否存在,如果不存在,則建立一個空陣列,並將計畫添加到該陣列中。
Object.groupBy()
可以透過以下方式來使用新的
Object.groupBy
方法,程式碼更簡潔:
const fruitsByColor =Object.groupBy(fruits,(fruit)=> fruit.color);
需要註意,使用
Object.groupBy
方法返回一個沒有原型(即沒有繼承任何內容和方法)的物件。這意味著該物件不會繼承
Object.prototype
上的任何內容或方法,例如
hasOwnProperty
或
toString
等。雖然這樣做可以避免意外覆蓋
Object.prototype
上的內容,但也意味著不能使用一些與物件相關的方法。
const fruitsByColor =Object.groupBy(fruits,(fruit)=> fruit.color);
console.log(fruitsByColor.hasOwnProperty("red"));
// TypeError: fruitsByColor.hasOwnProperty is not a function
在呼叫
Object.groupBy
時,傳遞給它的回呼函式應該返回一個字串或
Symbol
型別的值。如果回呼函式返回其他型別的值,它將被強制轉換為字串。
瀏覽器支持:
Map.groupBy()
Map.groupBy
和
Object.groupBy
的功能一樣,只是返回的結果型別不同。
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);
這雷根據水果顏色進行了分組,輸出結果如下:
可以透過 Map 的
get
方法來來獲取分組分組結果:
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字串應該只包含正確配對的代理對或單獨的BMP字元。
下面來看一個例子
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
如果傳遞的字串格式不正確,
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()
函式的使用場景主要包括以下幾種情況:
數據驗證 :當你從外部源(如使用者輸入、檔、網路請求等)接收字串時,你可能想要驗證這些字串是否包含有效的UTF-16編碼。如果字串包含孤立的代理項(即沒有配對的高代理或低代理),那麽它可能不是有效的UTF-16字串,這可能會導致後續處理時出錯。
文本處理 :在處理文本數據(如搜尋、排序、轉換等)時,確保文本是有效編碼的非常重要。如果文本包含錯誤的編碼,那麽處理結果可能會是不正確的或不可預測的。
網路傳輸 :當你透過網路發送或接收文本數據時,確保數據的編碼是正確的至關重要。錯誤的編碼可能導致數據在傳輸過程中被損壞,或者在接收端無法正確解析。
資料庫儲存 :在將文本數據儲存到資料庫之前,驗證其編碼的正確性也是一個好習慣。這可以確保數據的完整性和可讀性,並避免在後續查詢或處理時出現問題。
使用者介面顯示 :在使用者介面中顯示文本時,確保文本是有效編碼的也很重要。錯誤的編碼可能導致文本無法正確顯示,或者顯示出不正確的字元。
瀏覽器支持:
String.prototype.toWellFormed()
toWellFormed()
方法返回一個字串,其中該字串的所有單獨代理項都被替換為 Unicode 替換字元 U+FFFD。
toWellFormed()
叠代字串的碼元,並將任何單獨代理項替換為 Unicode 替換字元 U+FFFD。這確保了返回的字串格式正確並可用於期望正確格式字串的函式,比如 encodeURI。由於引擎能夠直接存取字串的內部表示,與自訂實作相比
toWellFormed()
更高效。
const strings =[
// 單獨的前導代理
"ab\uD800",
"ab\uD800c",
// 單獨的後尾代理
"\uDFFFab",
"c\uDFFFab",
// 格式正確
"abc",
"ab\uD83D\uDE04c",
];
for(const str of strings){
console.log(str.toWellFormed());
}
// 輸出:
// "ab"
// "abc"
// "ab"
// "cab"
// "abc"
// "ab😄c"
如果傳遞的字串格式不正確,
encodeURI
會丟擲錯誤。可以先透過使用
toWellFormed()
將字串轉換為格式正確的字串來避免這種情況。
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=�"
瀏覽器支持:
ArrayBuffer:resize / transfer
ArrayBuffer.prototype.resize
ArrayBuffer
例項的
resize()
方法將
ArrayBuffer
調整為指定的大小,以字節為單位,前提是該
ArrayBuffer
是可調整大小的並且新的大小小於或等於該
ArrayBuffer
的
maxByteLength
。
const buffer =newArrayBuffer(8,{maxByteLength:16});
console.log(buffer.byteLength);// 8
if(buffer.resizable){
console.log("緩沖區大小是可調整的!");
buffer.resize(12);
}
註意:
如果
ArrayBuffer
已分離或不可調整大小,則丟擲該錯誤。
如果
newLength
大於該
ArrayBuffer
的
maxByteLength
,則丟擲該錯誤。
瀏覽器支持:
ArrayBuffer.prototype.transfer
transfer()
方法執行與結構化複制演算法相同的操作。它將當前
ArrayBuffer
的字節復制到一個新的
ArrayBuffer
物件中,然後分離當前
ArrayBuffer
物件,保留了當前
ArrayBuffer
的大小可調整性。
// 建立一個 ArrayBuffer 並寫入一些字節
const buffer =newArrayBuffer(8);
const view =newUint8Array(buffer);
view[1]=2;
view[7]=4;
// 將緩沖區復制到另一個相同大小的緩沖區
const buffer2 = buffer.transfer();
console.log(buffer.detached);// true
console.log(buffer2.byteLength);// 8
const view2 =newUint8Array(buffer2);
console.log(view2[1]);// 2
console.log(view2[7]);// 4
// 將緩沖區復制到一個更小的緩沖區
const buffer3 = buffer2.transfer(4);
console.log(buffer3.byteLength);// 4
const view3 =newUint8Array(buffer3);
console.log(view3[1]);// 2
console.log(view3[7]);// undefined
// 將緩沖區復制到一個更大的緩沖區
const buffer4 = buffer3.transfer(8);
console.log(buffer4.byteLength);// 8
const view4 =newUint8Array(buffer4);
console.log(view4[1]);// 2
console.log(view4[7]);// 0
// 已經分離,丟擲 TypeError
buffer.transfer();// TypeError: Cannot perform ArrayBuffer.prototype.transfer on a detached ArrayBuffer
瀏覽器支持:
Atomics.waitAsync()
Atomics.waitAsync()
靜態方法異步等待共享記憶體的特定位置並返回一個 Promise。與
Atomics.wait()
不同,
waitAsync
是非阻塞的且可用於主執行緒。
下面來看一個簡單的例子,給定一個共享的
Int32Array
。
const sab =newSharedArrayBuffer(1024);
const int32 =newInt32Array(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")。
瀏覽器支持:
正規表式 v 標誌
RegExp 的
v
標誌是
u
標誌的超集,並提供了另外兩個功能:
字串的 Unicode 內容 :透過 Unicode 內容轉義,可以使用字串的內容。
const re =/^\p{RGI_Emoji}$/v;
// 匹配僅包含 1 個程式碼點的表情符號:
re.test('⚽');// '\u26BD' // true
// 匹配由多個程式碼點組成的表情符號:
re.test('👨🏾⚕️');// '\u{1F468}\u{1F3FE}\u200D\u2695\uFE0F' // true
設定符號 :允許在字元類之間進行集合操作。
const re =/[\p{White_Space}&&\p{ASCII}]/v;
re.test('\n');// true
re.test('\u2028');// false
那 u 標誌是幹什麽的呢?
在 JavaScript 中,RegExp 物件的 u 標誌(也稱為 Unicode 標誌)是用於處理 Unicode 字元的。當在正規表式字面量或建構函式中使用 u 標誌時,它將改變正規表式的行為,以便更準確地處理 Unicode 字元。以下是 u 標誌的主要作用:
完整 Unicode 字元匹配 : 在 Unicode 中,有些字元是由多個「程式碼單元」(在 UTF-16 編碼中)組成的,如表情符號或某些特殊字元。不使用 u 標誌時,正規表式可能會將這些字元拆分成多個程式碼單元,導致不正確的匹配。使用 u 標誌後,正規表式會將整個字元視為一個單位,從而進行更準確的匹配。
Unicode 內容的支持 : 使用 u 標誌的正規表式可以存取 Unicode 字元的內容,如是否為大寫字母、是否為標點符號等。這可以透過在正規表式中使用 \p{...} 和 \P{...} 序列來實作,其中 {...} 中是 Unicode 內容的名稱。
Unicode 字元類別的支持 : 與 Unicode 內容類似,使用 u 標誌的正規表式還可以支持 Unicode 字元類別,如 \p{Letter} 匹配任何字母字元,\p{Number} 匹配任何數位字元等。
點號(.)的新行為 : 在預設情況下,正規表式中的點號(.)不匹配換行符,但會匹配任何單個字元(在 UTF-16 編碼中是一個程式碼單元)。但是,當使用 u 標誌時,點號將匹配任何單個 Unicode 字元,包括那些由多個 UTF-16 程式碼單元組成的字元。
對量詞和邊界斷言的改進 : 使用 u 標誌的正規表式將改進對 Unicode 字元的量詞(如 *, +, ?, {n}, {n,}, {n,m})和邊界斷言(如 ^ 和 $)的處理。這確保了它們在整個 Unicode 字元上正確工作,而不是僅僅在 UTF-16 程式碼單元上。
更準確的字元類 : 使用 u 標誌的正規表式將更準確地解釋字元類,如 [a-z]。在不使用 u 標誌時,這個字元類可能只匹配基本的拉丁字母。但是,使用 u 標誌後,它將根據 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。