ES13中五個最具變革性的JavaScript特性
ES13包含了許多有價值的特性,徹底改變了我們編寫JavaScript的方式。
從異步升級到數組語法糖等等,讓我們來看看這些特性,看看你是否錯過了其中一些。
1. 頂級await
在ES13之前,我們永遠不能在全局作用域中使用await。
? 之前:
// X 語法錯誤:await 只在異步函數中有效
await setTimeoutAsync(3000);
function setTimeoutAsync(timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('codingbeautydev.com');
}, timeout);
});
}
我們總是必須將其放在async函數中或創建一個async IIFE(立即執行函數表達式):
// 異步立即執行函數
(async () => {
await setTimeoutAsync(3000);
})();
// 類似 C++
async function main() {
await setTimeoutAsync(3000);
}
? ES13之后:
// ? 等待超時 - 沒有拋出錯誤
await setTimeoutAsync(3000);
function setTimeoutAsync(timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('codingbeautydev.com');
}, timeout);
});
}
2. 類聲明升級
(1)類字段聲明
在ES13之前,我們只能在構造函數中聲明類字段: 與許多其他語言不同,我們不能在類的最外層作用域中聲明或定義它們。
? 之前:
? 現在有了ES13: 就像在TypeScript中一樣:
(2)私有方法和字段
在ES13之前,創建私有方法是不可能的。 我們還必須使用丑陋的下劃線hack來表示私有性 — 但那只是一個指示。
? 之前:
class Person {
_firstName = 'Tari';
_lastName = 'Ibaba';
get name() {
return `${this._firstName} ${this._lastName}`;
}
}
const person = new Person();
console.log(person.name); // Tari Ibaba
// 我們仍然可以訪問私有成員!
console.log(person._firstName); // Tari
console.log(person._lastName); // Ibaba
// 它們也可以被修改!
person._firstName = 'Lionel';
person._lastName = 'Messi';
console.log(person.name); // Lionel Messi
? ES13之后:
我們可以通過在字段前加上井號(#
)來為類添加私有字段和成員:
如果你試圖從類外部訪問它,你會得到一個語法錯誤:
class Person {
#firstName = 'Tari';
#lastName = 'Ibaba';
get name() {
return `${this.#firstName} ${this.#lastName}`;
}
}
const person = new Person();
console.log(person.name);
// 語法錯誤:私有字段 '#firstName' 必須在封閉的類中聲明
console.log(person.#firstName);
console.log(person.#lastName);
我們可以從錯誤消息中看到一些有趣的東西:
編譯器甚至不期望你從類外部嘗試訪問私有字段 — 它假設你是在嘗試創建一個。
(3)靜態類字段和靜態私有方法
靜態字段 — 類本身的屬性,而不是任何特定實例的屬性。
自ES13以來,我們現在可以輕松地為任何類創建它們:
class Person {
static #count = 0;
static eyeCount = 2;
static getCount() {
// 使用 this 訪問同級靜態成員
return this.#count;
}
// 實例成員
constructor() {
// 使用 this.constructor 訪問靜態成員
this.constructor.#incrementCount();
}
static #incrementCount() {
this.#count++;
}
}
const person1 = new Person();
const person2 = new Person();
console.log(Person.getCount()); // 2
3. 數組升級:新的at()方法
通常我們會使用方括號([])來訪問數組的第N個元素。
const arr = ['a', 'b', 'c', 'd'];
console.log(arr[1]); // b
但從末尾訪問第N個項目一直是一個痛點 -- 我們必須使用arr.length - N進行索引:
? ES13之前:
const arr = ['a', 'b', 'c', 'd'];
// 倒數第1個元素
console.log(arr[arr.length - 1]); // d
// 倒數第2個元素
console.log(arr[arr.length - 2]); // c
幸運的是,ES13帶來了一個新的at()方法,解決了所有這些問題:
const str = 'Coding Beauty';
console.log(str.at(-1)); // y 倒數第1個字符
console.log(str.at(-2)); // t 倒數第2個字符
4. 靜態類塊
隨著靜態字段的出現,靜態塊也來了。 只在類創建時執行一次代碼 — 就像C#和Java等OOP語言中的靜態構造函數。 所以你可以在類中創建任意多個靜態塊 — 所有代碼都會按你定義的順序運行:
class Vehicle {
static defaultColor = 'blue';
}
class Car extends Vehicle {
static colors = [];
// ?? pushes red before green
// ?? 先添加 red,然后添加 green
static {
this.colors.push(super.defaultColor, 'red');
}
static {
this.colors.push('green');
}
}
console.log(Car.colors); // ['blue', 'red', 'green']
5. 錯誤報告升級
有時我們捕獲調用棧下方方法的錯誤,只是為了將其重新拋出回調用棧上方。 但當我們這樣做時,我們會失去原始錯誤中的關鍵信息:
try {
userAction();
} catch (err) {
// ? doesn't know fundamental cause of error
// ? 不知道錯誤的根本原因
console.log(err);
}
function userAction() {
try {
apiCallThatCanThrow();
} catch (err) {
// ?? rethrow
// ?? 重新拋出錯誤
throw new Error('New error message');
}
}
function apiCallThatCanThrow() {
console.log('fetching from codingbeautydev.com...');
throw new Error('throwing for no reason');
}
這就是為什么ES13引入了一個新的cause屬性來保留這個重要信息并使調試更容易:
try {
userAction();
} catch (err) {
// ? now knows what caused the error
// ? 現在知道了錯誤的原因
console.log(err);
console.log(`Caused by: ${err.cause}`);
}
function userAction() {
try {
apiCallThatCanThrow();
} catch (err) {
// ? error cause
// ? 錯誤原因
throw new Error('New error message', { cause: err });
}
}
function apiCallThatCanThrow() {
console.log('fetching from codingbeautydev.com...');
throw new Error('throwing for no reason');
}
最后的思考
總的來說,ES13對JavaScript來說是一個重大飛躍,它帶來了幾個已成為現代開發必不可少的特性。 使你能夠編寫更清晰、更簡潔、更具表現力的代碼。