ES2024 新特性介紹,改變許多未來的開發方式 (Javascript)

#軟體技術

ES2024 新特性介紹,改變許多未來的開發方式 (Javascript)

ES2024 (ES15) 已在今年 (2024) 7 月正式發佈。這個版本帶來了許多令人興奮且等待已久的功能,也讓 js 朝現代化語言更前進了一步。完整的官方標準請見 ECMAScript® 2024 Language Specification

如果您想先回顧 ES2020-2023 的改變內容,可以先參考我們過去的文章:

而今天,就讓夏格飛來替大家導覽 ES2024 的全新功能吧,這次不只加入了社群期待以久的重要功能,也包含一些可能改變未來寫法的新特性以及語法,讓我們看下去。

Promise.withResolvers()

Promise 終於可以簡單的從外部進行 resolve 了,我們不用再自己落落長的寫 Workaround 或是用第三方 Deferred 物件來處理了。

以前

let resolveIt;

new Promise((resolve) => {
  resolveIt = resolve;
});

resolveIt() // To resolve

現在

const { promise, resolve, reject } = Promise.withResolvers();

用在某些 callback 時 resolve Promise 非常方便

const { promise, resolve } = Promise.withResolvers();

// Some event
foo.addEventListener('load', () => {
  resolve();
});

await promise;

groupBy()

新的 Object.groupBy()Map.groupBy() 可以讓我們快速群組一系列物件。

// Create an Array
const fruits = [
  {name: "apples", quantity: 300},
  {name: "bananas", quantity: 500},
  {name: "oranges", quantity: 200},
  {name: "kiwi", quantity: 150}
];

const result = Object.groupBy(fruits, function ({ quantity }) {
  return quantity > 200 ? "ok" : "low";
});

/*
{
    "ok": [
        {
            "name": "apples",
            "quantity": 300
        },
        {
            "name": "bananas",
            "quantity": 500
        }
    ],
    "low": [
        {
            "name": "oranges",
            "quantity": 200
        },
        {
            "name": "kiwi",
            "quantity": 150
        }
    ]
}
*/

Unicode 字串的 toWellFormed() 字串格式化

Unicode 字串對於表示來自不同語言和符號的各種字元非常重要。此次更新將確保在不同的 JavaScript 環境中一致且準確地處理這些字串。

在過去,只要遇到無效的 Unicode 字元,在顯示時都會轉成 也就是 U+FFFD 來顯示,稱為 surrogate 。但字串本身的編碼還是沒有變,所以當遇到某些函式要處理字串時,就會跳出 Malformed unicode character 之類的錯誤訊息。

toWellFormed() 可以將字串中任何不正確的 Unicode 代碼統一轉成 U+FFFD,這樣各類字串函式就可以正常處理了。

const sampleStrings = [
  // 各種帶有 surrogate 的異常範例
  "igor\uD800",
  "igor\uD800komolov",
  "\uDC00yourfuse",
  "your\uDC00fuse",

  // 正確格式
  "yourFuse",
  "emoji\uD83D\uDE00",
];

sampleStrings.forEach(str => {
  console.log(`Processed String: ${str.toWellFormed()}`);
});

// Expected output:
// "Processed String: igor�"
// "Processed String: igor�komolov"
// "Processed String: �yourfuse"
// "Processed String: your�fuse"
// "Processed String: yourFuse"
// "Processed String: emoji😀"

上面的範例利用 toWellFormed() 將字串中有錯誤的 Unicode 轉換成固定的 。這確保返回的字串格式良好,並且可以在需要格式良好的字串的函數中使用。

下面的範例展示了原本常用的 encodeURI() 遇到錯誤 Unicode 時,會直接跳 Error,但先使用 toWellFormed() 可以有效的轉成可用字元(U+FFFD 編碼為%EF%BF%BD

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"

Atomic.waitAsync()

他是對應 Atomic.wait() 的功能,原本的 Atomic.wait() 是用來等待一個 SharedArray 中的某個位置返回 promise,主要用在共享記憶體的情境下使用,稱為原子操作,參見 MDN Atomic

而原本的 Atomic.wait() 是 sync 的,會造成 UI 介面鎖死,所以不太能用在瀏覽器上。新的 Atomic.waitAsync() 將可以轉成異步等待,不再鎖死畫面。

const sab = new SharedArrayBuffer(1024);
const int32 = new Int32Array(sab);

const result = Atomics.waitAsync(int32, 0, 0, 1000);
// { async: true, value: Promise {<pending>} }

Atomics.notify(int32, 0);
// { async: true, value: Promise {<fulfilled>: 'ok'} }

正規表示式新修飾符: v 與字串操作

JavaScript 中正規表示式的這種改進允許更複雜的模式匹配和字串操作。 「v」修飾符和字串操作符號可實現更精確和更具可讀性的正規表示式。例如,您可以使用此功能來匹配具有特定 Unicode 屬性的一組字元。

// 字串 diff 或排除
[A--B]

// 字串交集
[A&&B]

// 嵌套字元類別
[A--[0-9]]

頂層 await (Top-level await)

終於,等了很久的 Top-level await 成為正式標準了。在過去幾年早就被用在無數專案了,由於這個性質不能用 Babel Polyfill,所以在現代化瀏覽器紛紛提早支援的情況下,一旦用了這個功能就表示完全放棄 IE 支援。而現在則是成為正式標準了。

// With top-level await
const data = await fetchData();
console.log(data);

管道 (Pipeline) 運算符

管道運算符 (|>) 透過多個函數呼叫提高了程式碼的可讀性。它允許使用函數式語法,其中表達式的結果作為參數傳遞給下一個函數。例如,您可以將巢狀函數呼叫重構為可讀性高的操作序列:

// Before
const calculatedValue = Math.ceil(Math.pow(Math.max(0, -10), 1/3));

// After
const calculatedValue = -10
  |> (n => Math.max(0, n))
  |> (n => Math.pow(n, 1/3))
  |> Math.ceil;

雖然很多函式本來就支持鏈狀呼叫,例如下面陣列的鏈狀呼叫:

arr.filter(...)
  .map(...)
  .join(',')

但是管道操作符進一步讓鏈狀呼叫擴及到所有函式上面,對未來的開發手法會造成廣泛的影響。

Record 與 Tuple 型別

Record 與 Tuple 分別是物件與陣列的不可變版本,創建後便無法修改。更新 Record 或 Tuple 會產生新物件:

// 建立 Record
const userProfile = #{
  username: "IgorKomolov",
  age: 39,
};

// 建立 Tuple
const numberSequence = #[10, 20, 30];

// 更新他們,將回傳新實例
const updatedProfile = userProfile.with({ age: 40});
console.log(updatedProfile); // #{ username: "IgorKomolov", age: 40 }
console.log(userProfile); // #{ username: "IgorKomolov", age: 39 } (原本的不變)

const newNumberSequence = numberSequence.with(1, 25);
console.log(newNumberSequence); // #[10, 25, 30]
console.log(numberSequence); // #[10, 20, 30] (原本的不變)

裝飾器 (Decorator)

認真,這個大家用到爛的功能現在才正式推出?

總而言之,感謝 TypeScript 預先帶來這個機制,多年後 JS 終於以相近的語法推出了裝飾器,現在開箱即用!

這功能允許修改或增強類別、方法、屬性或參數的行為。它們對於以聲明方式添加元資料、日誌記錄或修改行為特別有用:

@Component
class SampleClass {
  @TrackExecution
  action(parameter1, parameter2) {
    //
  }
}

另外要注意,function 無法掛上裝飾器喔。原因是 function 是 JS 一級公民,在初始化編譯時就會提早宣告了,所以裝飾器來不及在 function 上面生效。這個提早宣告的行為稱為 Hoist,也是為什麼 function 可以宣告在程式碼下面的原因。

相關文章可以參考 ECMAScript decorators on functions

取代 Date 的 Temporal API

Temporal 已經成為草案了一段時間,是為 JavaScript 提出的現代且全面的日期時間 API,目的是處理許多現有的 Date 難以涵蓋的複雜時間處理功能。下面是 Temporal 的一些範例:

取得 UTC 的當下時刻

Temporal.Now.instant().toString()

取得特定時區中的目前分區日期時間

Temporal.Now.zonedDateTimeISO('Asia/Taipei').toString()

取得 ISO 格式的目前純日期時間

Temporal.Now.plainDateTimeISO().toString()

取得 ISO 格式的目前普通時間

Temporal.Now.plainTimeISO().toString()

關於 ZonedDateTime

Temporal 中的類別ZonedDateTime具有多個屬性和方法,允許對日期時間資訊進行詳細操作和檢索。

  • 其中包括日曆、時區、年、月、日、小時、分鐘、秒甚至奈秒的 getter。

  • 它還包括.with().add().subtract().until().since()和等方法.round(),提供了處理分區日期時間值的廣泛功能。

Temporal 中的 Plain 時間類別

Temporal 引入了「Plain」類別,它們是沒有時區的時間的抽象表示。

  • 這些類別包括PlainDateTimePlainDatePlainTime

  • 它們對於顯示給定時區的時間或與時區無關的時間計算非常有用,例如尋找 1984 年 6 月的第一個星期二。

這些範例示範了 ES2024 中的 Temporal 如何簡化和增強 JavaScript 中的日期時間處理,為開發人員提供更強大、更通用的工具。


const now = Temporal.Now.zonedDateTimeISO('America/New_York');
console.log(now.toString());

const date = Temporal.PlainDate.from('2024-01-01');
const newDate = date.add({ days: 10 });
console.log(newDate.toString()); // Outputs '2024-01-11'

更多介紹

強化的私有屬性

現在,私有屬性不必使用 constructor 定義了,可以直接宣告

class User {
  username;
  dateOfBirth;
  #address;
}

也可以建立 getter

class User {
  username;
  dateOfBirth;
  #address;
  get #address() {
    return #address;
  }
}

同時簡化了物件類型的檢查,使類型檢查更加直覺且不易出錯。下面的範例,展示了函式可能要檢查不同類型的物件是否包含私有屬性欄位,由於物件可能不同,只能用 try catch 處理。

class Book {
    #author;

    constructor(author) {
        this.#author = author;
    }

    static hasAuthorField(obj) {
        try {
            obj.#author; // 確認物件的私有屬性是否存在
            return true;
        } catch (err) {
            if (err instanceof TypeError) {
                return false; // 不存在
            }
            throw err; // 錯誤
        }
    }
}

// 示範
const myBook = new Book("Igor Komolov");
console.log(Book.hasAuthorField(myBook)); // 應該要是 true

const otherObject = {};
console.log(Book.hasAuthorField(otherObject)); // 應該要是 false

ES2024 之後,可以用 in 檢查

class BookES2024 {
    #author;

    constructor(author) {
        this.#author = author;
    }

    static hasAuthorField(obj) {
        return #author in obj; // 新的 2024 私有屬性檢查方式
    }
}

Realms API 領域隔離

此 API 提供了一種建立隔離 JavaScript 環境的機制。它對於安全程式碼執行和沙箱非常有用,允許您在受控和隔離的環境中運行程式碼。(領域展開?)

展開我的領域吧!

const myRealm = new Realm();
myRealm.evaluate('3 * 5'); // 15

共享 Symbols

const myRealm = new Realm();
Symbol.for('y') === myRealm.evaluate('Symbol.for("y")'); // true

包裹成函式

const myRealm = new Realm();
const doubleFunction = myRealm.evaluate('num => num * 2');
doubleFunction(10); // 20

送入 callback

const myRealm = new Realm();
const processNumber = myRealm.evaluate('(number, callback) => callback(number + 5)');
processNumber(5, (result => console.log(result))); // Logs 10 (5 + 5)

不可存取全域物件

const myRealm = new Realm();
myRealm.evaluate('this'); // Throws a TypeError
myRealm.evaluate('new Array()'); // Throws a TypeError
myRealm.evaluate('Object.keys({})'); // Throws a TypeError

這功能很適合替代 eval() 作為客製化語法的核心功能,可以預見他能讓許多模版套件的程式碼減少許多。

總結

這次 ES2024 的新特性比往年一口氣加入的多,還都是許多等待以久,大家偷跑用了好幾年的功能,如 top-level await 與 decorator。也包含許多將改變未來編寫模式的特性,如 Promise.withResolvers、pipelines 與 Temporal 。

我們可以預期的是 ECMAScript 持續在朝更新,更現代化的方向走。而 Node.js 與 TypeScript 的整合,也帶領我們前往更安全、更嚴謹的路線。2024 以後的 Javascript 如何發展,值得我們拭目以待。

如果您還想知道更多網站開發技術的新知,歡迎追蹤夏格飛: Threads | Facebook

相關文章