11個 ES2022(ES13)中驚人的 JavaScript 新特性

語言: CN / TW / HK

與許多其他程式語言一樣,JavaScript 也在不斷髮展,每年,該語言都會通過新功能變得更強大,讓開發人員編寫更具表現力和簡潔的程式碼。

讓我們探索 ECMAScript 2022 (ES13) 中新增的最新功能,並檢視它們的使用示例以便我們更好地理解它們。

1、類欄位宣告

在 ES13 之前,類欄位只能在建構函式中宣告,與許多其他語言不同,我們不能在類的最外層範圍內宣告或定義它們。

class Car {
 constructor() {
   this.color = 'blue';
   this.age = 2;
 }
}
const car = new Car();
console.log(car.color); // blue
console.log(car.age); // 2

ES13 消除了這個限制,現在我們可以編寫如下程式碼:

class Car {
 color = 'blue';
 age = 2;
}
const car = new Car();
console.log(car.color); // blue
console.log(car.age); // 2

2、私有方法和欄位

以前,不能在類中宣告私有成員,成員通常以下劃線 (_) 為字首,表示它是私有的,但仍然可以從類外部訪問和修改。

class Person {
 _firstName = 'Joseph';
 _lastName = 'Stevens';
 get name() {
   return `${this._firstName} ${this._lastName}`;
 }
}
const person = new Person();
console.log(person.name); // Joseph Stevens
// Members intended to be private can still be accessed
// from outside the class
console.log(person._firstName); // Joseph
console.log(person._lastName); // Stevens
// They can also be modified
person._firstName = 'Robert';
person._lastName = 'Becker';
console.log(person.name); // Robert Becker

使用 ES13,我們現在可以將私有欄位和成員新增到類中,方法是在其前面加上井號 (#),試圖從類外部訪問它們會導致錯誤:

class Person {
 #firstName = 'Joseph';
 #lastName = 'Stevens';
 get name() {
   return `${this.#firstName} ${this.#lastName}`;
 }
}
const person = new Person();
console.log(person.name);
// SyntaxError: Private field '#firstName' must be
// declared in an enclosing class
console.log(person.#firstName);
console.log(person.#lastName);

請注意,這裡丟擲的錯誤是語法錯誤,發生在編譯時,因此沒有部分程式碼執行,編譯器甚至不希望您嘗試從類外部訪問私有欄位,因此,它假定您正在嘗試宣告一個。

3、await運算子

在 JavaScript 中,await 運算子用於暫停執行,直到 Promise 被解決(履行或拒絕)。

以前,我們只能在 async 函式中使用此運算子 - 使用 async 關鍵字宣告的函式。我們無法在全球範圍內這樣做。

function setTimeoutAsync(timeout) {
 return new Promise((resolve) => {
   setTimeout(() => {
     resolve();
   }, timeout);
 });
}
// SyntaxError: await is only valid in async functions
await setTimeoutAsync(3000);

使用 ES13,現在我們可以:

function setTimeoutAsync(timeout) {
 return new Promise((resolve) => {
   setTimeout(() => {
     resolve();
   }, timeout);
 });
}
// Waits for timeout - no error thrown
await setTimeoutAsync(3000);

4、靜態類欄位和靜態私有方法

我們現在可以在 ES13 中為類宣告靜態欄位和靜態私有方法,靜態方法可以使用 this 關鍵字訪問類中的其他私有/公共靜態成員,例項方法可以使用 this.constructor 訪問它們。

class Person {
 static #count = 0;
 static getCount() {
   return this.#count;
 }
 constructor() {
   this.constructor.#incrementCount();
 }
 static #incrementCount() {
   this.#count++;
 }
}
const person1 = new Person();
const person2 = new Person();
console.log(Person.getCount()); // 2

5、類靜態塊

ES13 允許在建立類時定義只執行一次的靜態塊,這類似於其他支援面向物件程式設計的語言(如 C# 和 Java)中的靜態建構函式。

一個類的類主體中可以有任意數量的靜態 {} 初始化塊,它們將與任何交錯的靜態欄位初始值設定項一起按照宣告的順序執行,我們可以在靜態塊中使用超屬性來訪問超類的屬性。

class Vehicle {
 static defaultColor = 'blue';
}
class Car extends Vehicle {
 static colors = [];
 static {
   this.colors.push(super.defaultColor, 'red');
 }
 static {
   this.colors.push('green');
 }
}
console.log(Car.colors); // [ 'blue', 'red', 'green' ]

6、私人領域的人體工程學品牌檢查

我們可以使用這個新特性來檢查一個物件中是否有一個特定的私有欄位,使用 in 運算子。

class Car {
 #color;
 hasColor() {
   return #color in this;
 }
}
const car = new Car();
console.log(car.hasColor()); // true;

in 運算子可以正確區分不同類的同名私有欄位:

class Car {
 #color;
 hasColor() {
   return #color in this;
 }
}
class House {
 #color;
 hasColor() {
   return #color in this;
 }
}
const car = new Car();
const house = new House();
console.log(car.hasColor()); // true;
console.log(car.hasColor.call(house)); // false
console.log(house.hasColor()); // true
console.log(house.hasColor.call(car)); // false

7、at() 方法進行索引

我們通常在 JavaScript 中使用方括號 ([]) 來訪問陣列的第 N 個元素,這通常是一個簡單的過程,我們只訪問陣列的 N - 1 屬性。

const arr = ['a', 'b', 'c', 'd'];
console.log(arr[1]); // b

但是,如果我們想使用方括號訪問陣列末尾的第 N 個專案,我們必須使用 arr.length - N 的索引。

const arr = ['a', 'b', 'c', 'd'];
// 1st element from the end
console.log(arr[arr.length - 1]); // d
// 2nd element from the end
console.log(arr[arr.length - 2]); // c

新的 at() 方法讓我們可以更簡潔、更有表現力地做到這一點,要訪問陣列末尾的第 N 個元素,我們只需將負值 -N 傳遞給 at()。

const arr = ['a', 'b', 'c', 'd'];
// 1st element from the end
console.log(arr.at(-1)); // d
// 2nd element from the end
console.log(arr.at(-2)); // c

除了陣列,字串和 TypedArray 物件現在也有 at() 方法。

const str = 'Coding Beauty';
console.log(str.at(-1)); // y
console.log(str.at(-2)); // t
const typedArray = new Uint8Array([16, 32, 48, 64]);
console.log(typedArray.at(-1)); // 64
console.log(typedArray.at(-2)); // 48

8、 RegExp 匹配索引

這個新功能允許我們指定我們想要獲取給定字串中 RegExp 物件匹配的開始和結束索引。

以前,我們只能在字串中獲取正則表示式匹配的起始索引。

const str = 'sun and moon';
const regex = /and/;
const matchObj = regex.exec(str);
// [ 'and', index: 4, input: 'sun and moon', groups: undefined ]
console.log(matchObj);

我們現在可以指定一個 d 正則表示式標誌來獲取匹配開始和結束的兩個索引。

const str = 'sun and moon';
const regex = /and/d;
const matchObj = regex.exec(str);
/**
[
 'and',
 index: 4,
 input: 'sun and moon',
 groups: undefined,
 indices: [ [ 4, 7 ], groups: undefined ]
]
*/
console.log(matchObj);

設定 d 標誌後,返回的物件將具有包含開始和結束索引的 indices 屬性。

9、Object.hasOwn() 方法

在 JavaScript 中,我們可以使用 Object.prototype.hasOwnProperty() 方法來檢查物件是否具有給定的屬性。

class Car {
 color = 'green';
 age = 2;
}
const car = new Car();
console.log(car.hasOwnProperty('age')); // true
console.log(car.hasOwnProperty('name')); // false

但是,這種方法存在一定的問題,一方面,Object.prototype.hasOwnProperty() 方法不受保護 - 它可以通過為類定義自定義 hasOwnProperty() 方法來覆蓋,該方法可能具有與 Object.prototype.hasOwnProperty() 完全不同的行為。

class Car {
 color = 'green';
 age = 2;
 // This method does not tell us whether an object of
 // this class has a given property.
 hasOwnProperty() {
   return false;
 }
}
const car = new Car();
console.log(car.hasOwnProperty('age')); // false
console.log(car.hasOwnProperty('name')); // false

另一個問題是,對於使用 null 原型建立的物件(使用 Object.create(null)),嘗試對其呼叫此方法會導致錯誤。

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
// TypeError: obj.hasOwnProperty is not a function
console.log(obj.hasOwnProperty('color'));

解決這些問題的一種方法是使用呼叫 Object.prototype.hasOwnProperty Function 屬性上的 call() 方法,如下所示:

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(Object.prototype.hasOwnProperty.call(obj, 'color')); // true
console.log(Object.prototype.hasOwnProperty.call(obj, 'name')); // false

這不是很方便,我們可以編寫一個可重用的函式來避免重複自己:

function objHasOwnProp(obj, propertyKey) {
 return Object.prototype.hasOwnProperty.call(obj, propertyKey);
}
const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(objHasOwnProp(obj, 'color')); // true
console.log(objHasOwnProp(obj, 'name')); // false

不過沒有必要,因為我們可以使用新的內建 Object.hasOwn() 方法。與我們的可重用函式一樣,它接受物件和屬性作為引數,如果指定的屬性是物件的直接屬性,則返回 true。否則,它返回 false。

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;
console.log(Object.hasOwn(obj, 'color')); // true
console.log(Object.hasOwn(obj, 'name')); // false

10、錯誤原因

錯誤物件現在有一個 cause 屬性,用於指定導致即將丟擲的錯誤的原始錯誤。這有助於為錯誤新增額外的上下文資訊並幫助診斷意外行為,我們可以通過在作為第二個引數傳遞給 Error() 建構函式的物件上設定 cause 屬性來指定錯誤的原因。

function userAction() {
 try {
   apiCallThatCanThrow();
 } catch (err) {
   throw new Error('New error message', { cause: err });
 }
}
try {
 userAction();
} catch (err) {
 console.log(err);
 console.log(`Cause by: ${err.cause}`);
}

11、從最後一個數組查詢

在 JavaScript 中,我們已經可以使用 Array find() 方法在陣列中查詢通過指定測試條件的元素,同樣,我們可以使用 findIndex() 來查詢此類元素的索引。

雖然 find() 和 findIndex() 都從陣列的第一個元素開始搜尋,但在某些情況下,最好從最後一個元素開始搜尋。

在某些情況下,我們知道從最後一個元素中查詢可能會獲得更好的效能。例如,這裡我們試圖在陣列中獲取值 prop 等於 y 的專案。使用 find() 和 findIndex():

const letters = [
 { value: 'v' },
 { value: 'w' },
 { value: 'x' },
 { value: 'y' },
 { value: 'z' },
];
const found = letters.find((item) => item.value === 'y');
const foundIndex = letters.findIndex((item) => item.value === 'y');
console.log(found); // { value: 'y' }
console.log(foundIndex); // 3

這行得通,但是由於目標物件更靠近陣列的尾部,如果我們使用 findLast() 和 findLastIndex() 方法從末尾搜尋陣列,我們可以讓這個程式執行得更快。

const letters = [
 { value: 'v' },
 { value: 'w' },
 { value: 'x' },
 { value: 'y' },
 { value: 'z' },
];
const found = letters.findLast((item) => item.value === 'y');
const foundIndex = letters.findLastIndex((item) => item.value === 'y');
console.log(found); // { value: 'y' }
console.log(foundIndex); // 3

另一個用例可能要求我們專門從末尾搜尋陣列以獲取正確的專案。例如,如果我們想在數字列表中查詢最後一個偶數, find() 和 findIndex() 會產生錯誤的結果:

const nums = [7, 14, 3, 8, 10, 9];
// gives 14, instead of 10
const lastEven = nums.find((value) => value % 2 === 0);
// gives 1, instead of 4
const lastEvenIndex = nums.findIndex((value) => value % 2 === 0);
console.log(lastEven); // 14
console.log(lastEvenIndex); // 1

我們可以在呼叫 find() 和 findIndex() 之前呼叫陣列的 reverse() 方法來反轉元素的順序。

但是這種方法會導致陣列發生不必要的突變,因為 reverse() 會反轉陣列的元素。避免這種突變的唯一方法是製作整個陣列的新副本,這可能會導致大型陣列出現效能問題。

此外, findIndex() 仍然無法在反轉陣列上工作,因為反轉元素也意味著更改它們在原始陣列中的索引。要獲得原始索引,我們需要執行額外的計算,這意味著編寫更多程式碼。

const nums = [7, 14, 3, 8, 10, 9];
// Copying the entire array with the spread syntax before
// calling reverse()
const reversed = [...nums].reverse();
// correctly gives 10
const lastEven = reversed.find((value) => value % 2 === 0);
// gives 1, instead of 4
const reversedIndex = reversed.findIndex((value) => value % 2 === 0);
// Need to re-calculate to get original index
const lastEvenIndex = reversed.length - 1 - reversedIndex;
console.log(lastEven); // 10
console.log(reversedIndex); // 1
console.log(lastEvenIndex); // 4

在 findLast() 和 findLastIndex() 方法派上用場的情況下。

const nums = [7, 14, 3, 8, 10, 9];
const lastEven = nums.findLast((num) => num % 2 === 0);
const lastEvenIndex = nums.findLastIndex((num) => num % 2 === 0);
console.log(lastEven); // 10
console.log(lastEvenIndex); // 4

這段程式碼更短,更易讀。最重要的是,它會產生正確的結果。

所以我們已經看到了 ES13 為 JavaScript 帶來的最新特性,使用它們來提高我們作為開發人員的工作效率,並以更簡潔和清晰的方式編寫更簡潔的程式碼。如果你覺得我這篇文章對你有用的話,請記得點讚我,關注我,並將它分享給你身邊的朋友,也許能夠幫助到他。

最後,感謝你的閱讀。