到了 2024 年 JavaScript 有什麼新特性?從 ES2020 到 ES2023 一次看

#軟體技術

到了 2024 年 JavaScript 有什麼新特性?從 ES2020 到 ES2023 一次看

身為開發人員,掌握 JavaScript 的最新動態,是讓程式碼更高效、現代且具有延展性的關鍵。

在這篇文章中,我們將一起探索 ES2020 到 ES2023 令人激動不已的 JavaScript 新特性,這在許多年前是難以想像的,這些理念不僅能啟發您,也能將開發過程帶來新的光景。

ES2020

Optional Chaining (?.)

ES2020 中引入的 Optional Chaining 可讓您讀取位於物件屬性鏈深處的值,而無需檢查鏈中的每個屬性是否存在。

let name = person?.address?.street?.name;

Nullish Coalescing (??)

ES2020 中也引入了空值合併運算符,如果一個表達式不為 null 或 undefined,則傳回第一個結果,否則傳回後面的表達式。

const person = {};

let name = person?.name ?? 'Unknown';

邏輯賦值操作符

以下操作符在 ES2020 皆可用

let x = 10;
x &&= 5; // 如果第一個值是true,則指派第二個值。
x ||= 5; // 如果第一個值是false,則指派第二個值。
x ??= 5; // 如果第一個值是undefined或null,則指派第二個值。

BigInt

BigInt 是 ES2020 中的一種新的基本型別,用於表示任意精確度的整數,從而允許使用大整數進行精確計算。

const x = 12345678901234567890n;
const y = 98765432123456789012n;

console.log(x + y);

延伸資源,如果您想要對原生 BigInt 進行一系列數學運算,請參考 bigint-toolkit

GlobalThis

新的全域物件 globalThis 提供了一種與現代 JavaScript 環境相容的存取全域物件的方法。

這個變數將傳統瀏覽器與 Node 環境各自使用的 window, this, global 等全域 root 物件集合在一起。

下面示範了在瀏覽器中, globalThis === window

console.log(globalThis === window); // true in a browser

matchAll()

String 原型物件上有一個新新方法 matchAll() ,可傳回一個迭代器,該迭代器產生正規表示式與字串的匹配,包括捕獲群組。

const regex = /(\w)(\d)/g;
const str = 'a1b2c3';

for (const match of str.matchAll(regex)) {
  console.log(match);
}

Promise.allSettled()

Promise API 上的一個新方法 allSettled() 非常實用,他會傳回一個新 Promise,當陣列中的所有子 Promises 都處理完成後(無論 resolve 或 reject),這個 Promise 就得到 resolve 狀態。

const promises = [
    Promise.resolve('a'), 
    Promise.reject('b'), 
    Promise.resolve('c')
];

Promise.allSettled(promises)
    .then((results) => console.log(results));

Dynamic import

JavaScript 中的動態匯入可讓您選擇非同步的匯入 JS 檔案,這就像我們長年在 Webpack 和 Babel 所做的那樣。

此功能將幫助您交付按需請求的程式碼,即程式碼分割,而無需 webpack 或其他模組捆綁器的消耗。如果您願意,也可以用 if-else 區分要載入的程式碼。

if (foo === bar) {
    import('./Baz').then((Baz) => {
       console.log(Baz.Baz);
    });
}

ES2021

Promise.any()

任何一個 Promise 成功就返回

Promise.any([promise1, promise2]).then((x) => {
  myDisplay(x);
});

如果所有的 Promise 都失敗,則 Promise 背拒絕且丟出 AggregateError,也就是找到的所有錯誤的集合。

Promise.any(promises).then((value) => {
  console.log(value);
}).catch((err) => { console.log("error: " + err) });

你可以用 AggregateError.errors 取得所有 Error 物件

String.replaceAll()

replaceAll 方法會將所有符合的字串替換為傳入的替換值。pattern 參數可以是字串或正則表達式模式,而 replacement 參數則可以是字串或函式。

const message = 'hello+good+morning';
const messageWithSpace = message.replaceAll('+', ' ');

// hello good morning

WeakRef and Finalizers

WeakRef 弱引用在其他語言已經很流行,它可以幫助我們建立快到更大物件​​的映射。由於 WeakRef 變數是對物件的唯一引用,因此當瀏覽器進行垃圾收集時,JavaScript 引擎可以安全地將其從記憶體中刪除並釋放空間。

const weakRef = new WeakRef({ 
  age: 13;
});

console.log(weakRef.deref().age)

// output: 13

WeakRef 和 Finalizers 通常會一起使用。FinalizationRegistry 方法讓你可以在物件被垃圾回收機制回收後請求執行一個回呼函式。

首先,你需要定義一個帶有回呼函式的 registry(註冊表)。接著,使用 .register 方法把你想觀察的物件註冊到這個 registry。這樣一來,當物件的記憶體被垃圾回收機制釋放時,你就會立即收到通知。

const registry = new FinalizationRegistry(value => {
  console.log("Finalizer");
});

registry.register({object: "Do Something"}, "testObject");

Numeric separators

這項新功能可讓您使用下劃線作為數字文字的分隔符號。它透過視覺上分隔數字分組來提高數字的可讀性。

// A billion
const amount = 1000000000; // Previous
const amount = 1_000_000_000; // With ES2021

// Hundreds of millions     
const amount = 1475938.38; // Previous
const amount = 1_475_938.38; // With ES2021

Private Accessors

Private Accessors 的運作方式與 Private Methods 非常相似,我們現在可以在 JavaScript 中定義私有的 getter 和 setter,使它們只能在類別內部或透過建立的實例存取。

class ExampleClass {
  get #Greeting() {return "Hello Good Morning !" 
  }

  get viewGreetingMsg() {
    return this.#Greeting
  }
}

let exampleClass = new ExampleClass();
console.log(exampleClass.viewGreetingMsg);

// Hello Good Morning !

Intl.DateTimeFormat 增加 style 選項

Intl.DateTimeFormat 可以啟用依語言調整的日期和時間格式**。**dateStyletimeStyle`選項允許我們請求給定長度的特定於區域設定的日期和時間。

// Time with short format let. 
let time = new Intl.DateTimeFormat('en' , { timeStyle: 'short' });
console.log(time .format(Date.now())); // 09:27 PM 

// Time with medium format.
let time  = new Intl.DateTimeFormat('en' , { timeStyle: 'medium'});
console.log(time .format(Date.now())); //09:27:50 PM 

// Time with long format. 
let time  = new Intl.DateTimeFormat('en' , { timeStyle: 'long' }) ;
console.log(time .format(Date.now())); // 11:27:50 PM GMT+11

ES2022

正規表示式匹配索引

JS 的 Regex 匹配功能,在某些情況下,您可能不僅需要匹配的 index,還需要每個捕獲的子字串的開始和結束索引。

透過這個新功能,您可以使用修飾符: d 來表示您想要指定符合字串的起始索引和結束索引。請參閱以下範例。

const animals = "Animals: Brown Bear, Koala, Polar Bear"
// with /d flag
const regex2 = /(Bear)/dg
console.log(...animals.matchAll(regex2))
// Output:
// [
//   'Bear',
//   'Bear',
//   index: 15,
//   input: "Animals: Brown Bear, Koala, Polar Bear",
//   groups: undefined,
//   indices: [ [15, 19], [15, 19], groups: undefined, length: 2 ]
// ] [
//   'Bear',
//   'Bear',
//   index: 34,
//   input: "Animals: Brown Bear, Koala, Polar Bear",
//   groups: undefined,
//   indices: [ [34, 38], [34, 38], groups: undefined, length: 2 ]// ]

Top-level await

儘管 async await 在 JavaScript 中已經使用了一段時間,但在 ES2022 之前,await僅在 async 範圍內可用。

在 ES2022 中,您可以在最外層的空間使用 await,並為您先前處理過的同步問題提供解決方案。例如,await會暫停模組及其父模組的渲染,直到匯入資源並確保事物得到正確協調。但這項特性導致他無法被 Polyfill 用在舊瀏覽器上。

// At top level of a JS file

const translationKeys = await import(/api/${language} );

const connection = await dbConnector();

String.prototype.at()

String 原型上的新方法 at() 傳回指定位置的字串,也可以接受負數值來取得字串末端起算的字元。

const str = 'hello';

console.log(str.at(0)); // 'h'
console.log(str.at(-1)); // 'o'

Error cause

也是非常實用的功能,Error 物件擁有新屬性 cause 允許您指定錯誤的造成原因。終於可以在 catch 裡面根據錯誤原因做不同的流程判斷了。

try {
  throw new Error('Error occurred', { cause: new Error('Underlying cause') });
} catch (error) {
  console.log(error.cause);
}

Object.hasOwn()

Object.hasOwn()hasOwnProperty() 方法的替代方法。儘管Object.prototype.hasOwnProperty已經出現在 JavaScript 規格中相當長一段時間了,但它有一些缺點,例如可以被覆蓋而造成失效,以及無法處理 Object.create(null) 建立出來的物件

let person = {
  hasOwnProperty: function() {
    return false;
  },
  age: 35
};

console.log(Object.hasOwn(person, 'age')); // true
console.log(person.hasOwnProperty('age')); // false

類別私有屬性

現在 class 可以擁有私有屬性,私有訪法,無論是動態還是靜態。也可以用 in 判斷私有欄位是否存在物件中。

class User {
  #userName;
  static #role;

  static #login() {
    this.#userName = "Hermione Granger";
  }

  static isLogin(user){
    return #userName in user && #login in user
  }
}

ES2023

從陣列最後搜尋

findLast()findLastIndex()函數將允許我們根據條件查找數組的最後一個到第一個元素。

const array = [{a: 1, b: 1}, {a: 2, b: 2}, {a: 3, b: 3}, {a: 4, b: 4}]

console.log(array.findLast(n => n)); //result -> {a: 4,b: 4 }

console.log(array.findLast(n => n.a * 5 === 20)); // result -> {a:4,b:4} as the condition is true so it returns the last element.

console.log(array.findLast(n => n.a * 5 === 21)); //result -> undefined as the condition is false so return undefined instead of {a:4,b:4}.

console.log(array.findLastIndex(n => n.a * 5 === 21)); // result -> -1 as the condition is not justified for returning the last element.

console.log(array.findLastIndex(n => n.a * 5 === 20)); // result -> 3 which is the index of the last element as the condition is true.

Hashbang 語法

此功能使我們能夠在某些 CLI 中使用 Hashbang / Shebang。類似於常用的 Bash 腳本。

#! 是腳本開頭的一個特殊指令,它告訴作業系統在執行腳本時使用哪個編譯器。

#!/usr/bin/env node

console.log(2*3);

Symbol 可作為 WeakMap 的 Key

這功能允許使用唯一的 Symbol 符號作為 key。目前 WeakMap 僅限於允許物件作為 key,之後就會方便許多。

const weak = new WeakMap();

const key = Symbol('my ref');
const someObject = { a:1 };

weak.set(key, someObject);
console.log(weak.get(key));

Immutable 陣列修改方法

Array.prototype 提供了一系列新的修改功能,但都會傳回新陣列,而不是修改原有陣列。

新引進的功能有:

  1. Array.prototype.toReversed()

  2. Array.prototype.toSorted(compareFn)

  3. Array.prototype.toSpliced(start, deleteCount, ...items)

  4. Array.prototype.with(index, value)

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]

/* toReversed */

const reversed = numbers.toReversed();
console.log("reversed", reversed); // "reversed", [9, 8, 7, 6, 5, 4, 3, 2, 1]
console.log("original", numbers); // "original", [1, 2, 3, 4, 5, 6, 7, 8, 9]

/* toSorted  */
const sortedArr = numbers.toSorted();
console.log("sorted", sortedArr); // "sorted", [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log("original", numbers); // "original", [1, 2, 3, 4, 5, 6, 7, 8, 9]

/* with */
const replaceWith = numbers.with(1, 100);
console.log("with", replaceWith); // "with", [1, 100, 3, 4, 5, 6, 7, 8, 9]
console.log("original", numbers); // "original", [1, 2, 3, 4, 5, 6, 7, 8, 9]

/* toSpliced */
const splicedArr = numbers.toSpliced(0, 4);
console.log("toSpliced", splicedArr); // "toSpliced", [5, 6, 7, 8, 9]
console.log("original", numbers); // "original", [1, 2, 3, 4, 5, 6, 7, 8, 9]

結論

JavaScript 的語法逐漸在進化,透過這些新穎且強大的語法,我們能夠提高程式碼的效率與可讀性,以解放產能帶來更多創造性與可能性。

除了這些特性以外,今年推出的 ES2024 (ES15) 也加入了非常多令人驚豔並期待已久的功能,請參考我們另一篇文章:

如果您還想知道更多新的 JS 特性或技術新知,歡迎關注 夏木樂 與我們的粉絲專頁

相關文章