當前位置: 妍妍網 > 碼農

ECMAScript 2024 正式釋出,新特性一覽!

2024-06-28碼農

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 標誌的主要作用:

    1. 完整 Unicode 字元匹配 : 在 Unicode 中,有些字元是由多個「程式碼單元」(在 UTF-16 編碼中)組成的,如表情符號或某些特殊字元。不使用 u 標誌時,正規表式可能會將這些字元拆分成多個程式碼單元,導致不正確的匹配。使用 u 標誌後,正規表式會將整個字元視為一個單位,從而進行更準確的匹配。

    2. Unicode 內容的支持 : 使用 u 標誌的正規表式可以存取 Unicode 字元的內容,如是否為大寫字母、是否為標點符號等。這可以透過在正規表式中使用 \p{...} 和 \P{...} 序列來實作,其中 {...} 中是 Unicode 內容的名稱。

    3. Unicode 字元類別的支持 : 與 Unicode 內容類似,使用 u 標誌的正規表式還可以支持 Unicode 字元類別,如 \p{Letter} 匹配任何字母字元,\p{Number} 匹配任何數位字元等。

    4. 點號(.)的新行為 : 在預設情況下,正規表式中的點號(.)不匹配換行符,但會匹配任何單個字元(在 UTF-16 編碼中是一個程式碼單元)。但是,當使用 u 標誌時,點號將匹配任何單個 Unicode 字元,包括那些由多個 UTF-16 程式碼單元組成的字元。

    5. 對量詞和邊界斷言的改進 : 使用 u 標誌的正規表式將改進對 Unicode 字元的量詞(如 *, +, ?, {n}, {n,}, {n,m})和邊界斷言(如 ^ 和 $)的處理。這確保了它們在整個 Unicode 字元上正確工作,而不是僅僅在 UTF-16 程式碼單元上。

    6. 更準確的字元類 : 使用 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。