11 個 ES2022(ES13)中驚人的 JavaScript 新特性
英文 | https://javascript.plainenglish.io/es13-javascript-features-eed7ed2f1497
翻譯 | 楊小愛
與許多其他編程語言一樣,JavaScript 也在不斷發(fā)展,每年,該語言都會通過新功能變得更強大,讓開發(fā)人員編寫更具表現(xiàn)力和簡潔的代碼。
讓我們探索 ECMAScript 2022 (ES13) 中添加的最新功能,并查看它們的使用示例以便我們更好地理解它們。
1、類字段聲明
在 ES13 之前,類字段只能在構(gòu)造函數(shù)中聲明,與許多其他語言不同,我們不能在類的最外層范圍內(nèi)聲明或定義它們。
class Car {
constructor() {
this.color = 'blue';
this.age = 2;
}
}
const car = new Car();
console.log(car.color); // blue
console.log(car.age); // 2
ES13 消除了這個限制,現(xiàn)在我們可以編寫如下代碼:
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,我們現(xiàn)在可以將私有字段和成員添加到類中,方法是在其前面加上井號 (#),試圖從類外部訪問它們會導(dǎo)致錯誤:
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);
請注意,這里拋出的錯誤是語法錯誤,發(fā)生在編譯時,因此沒有部分代碼運行,編譯器甚至不希望您嘗試從類外部訪問私有字段,因此,它假定您正在嘗試聲明一個。
3、await運算符
在 JavaScript 中,await 運算符用于暫停執(zhí)行,直到 Promise 被解決(履行或拒絕)。
以前,我們只能在 async 函數(shù)中使用此運算符 - 使用 async 關(guān)鍵字聲明的函數(shù)。我們無法在全球范圍內(nèi)這樣做。
function setTimeoutAsync(timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, timeout);
});
}
// SyntaxError: await is only valid in async functions
await setTimeoutAsync(3000);
使用 ES13,現(xiàn)在我們可以:
function setTimeoutAsync(timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, timeout);
});
}
// Waits for timeout - no error thrown
await setTimeoutAsync(3000);
4、靜態(tài)類字段和靜態(tài)私有方法
我們現(xiàn)在可以在 ES13 中為類聲明靜態(tài)字段和靜態(tài)私有方法,靜態(tài)方法可以使用 this 關(guān)鍵字訪問類中的其他私有/公共靜態(tài)成員,實例方法可以使用 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、類靜態(tài)塊
ES13 允許在創(chuàng)建類時定義只執(zhí)行一次的靜態(tài)塊,這類似于其他支持面向?qū)ο缶幊痰恼Z言(如 C# 和 Java)中的靜態(tài)構(gòu)造函數(shù)。
一個類的類主體中可以有任意數(shù)量的靜態(tài) {} 初始化塊,它們將與任何交錯的靜態(tài)字段初始值設(shè)定項一起按照聲明的順序執(zhí)行,我們可以在靜態(tài)塊中使用超屬性來訪問超類的屬性。
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、私人領(lǐng)域的人體工程學(xué)品牌檢查
我們可以使用這個新特性來檢查一個對象中是否有一個特定的私有字段,使用 in 運算符。
class Car {
#color;
hasColor() {
return #color in this;
}
}
const car = new Car();
console.log(car.hasColor()); // true;
in 運算符可以正確區(qū)分不同類的同名私有字段:
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 中使用方括號 ([]) 來訪問數(shù)組的第 N 個元素,這通常是一個簡單的過程,我們只訪問數(shù)組的 N - 1 屬性。
const arr = ['a', 'b', 'c', 'd'];
console.log(arr[1]); // b
但是,如果我們想使用方括號訪問數(shù)組末尾的第 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() 方法讓我們可以更簡潔、更有表現(xiàn)力地做到這一點,要訪問數(shù)組末尾的第 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
除了數(shù)組,字符串和 TypedArray 對象現(xiàn)在也有 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 對象匹配的開始和結(jié)束索引。
以前,我們只能在字符串中獲取正則表達式匹配的起始索引。
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);
我們現(xiàn)在可以指定一個 d 正則表達式標志來獲取匹配開始和結(jié)束的兩個索引。
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);
設(shè)置 d 標志后,返回的對象將具有包含開始和結(jié)束索引的 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 原型創(chuàng)建的對象(使用 Object.create(null)),嘗試對其調(diào)用此方法會導(dǎo)致錯誤。
const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
// TypeError: obj.hasOwnProperty is not a function
console.log(obj.hasOwnProperty('color'));
解決這些問題的一種方法是使用調(diào)用 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
這不是很方便,我們可以編寫一個可重用的函數(shù)來避免重復(fù)自己:
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
不過沒有必要,因為我們可以使用新的內(nèi)置 Object.hasOwn() 方法。與我們的可重用函數(shù)一樣,它接受對象和屬性作為參數(shù),如果指定的屬性是對象的直接屬性,則返回 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、錯誤原因
錯誤對象現(xiàn)在有一個 cause 屬性,用于指定導(dǎo)致即將拋出的錯誤的原始錯誤。這有助于為錯誤添加額外的上下文信息并幫助診斷意外行為,我們可以通過在作為第二個參數(shù)傳遞給 Error() 構(gòu)造函數(shù)的對象上設(shè)置 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、從最后一個數(shù)組查找
在 JavaScript 中,我們已經(jīng)可以使用 Array find() 方法在數(shù)組中查找通過指定測試條件的元素,同樣,我們可以使用 findIndex() 來查找此類元素的索引。
雖然 find() 和 findIndex() 都從數(shù)組的第一個元素開始搜索,但在某些情況下,最好從最后一個元素開始搜索。
在某些情況下,我們知道從最后一個元素中查找可能會獲得更好的性能。例如,這里我們試圖在數(shù)組中獲取值 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
這行得通,但是由于目標對象更靠近數(shù)組的尾部,如果我們使用 findLast() 和 findLastIndex() 方法從末尾搜索數(shù)組,我們可以讓這個程序運行得更快。
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
另一個用例可能要求我們專門從末尾搜索數(shù)組以獲取正確的項目。例如,如果我們想在數(shù)字列表中查找最后一個偶數(shù), find() 和 findIndex() 會產(chǎn)生錯誤的結(jié)果:
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
我們可以在調(diào)用 find() 和 findIndex() 之前調(diào)用數(shù)組的 reverse() 方法來反轉(zhuǎn)元素的順序。
但是這種方法會導(dǎo)致數(shù)組發(fā)生不必要的突變,因為 reverse() 會反轉(zhuǎn)數(shù)組的元素。避免這種突變的唯一方法是制作整個數(shù)組的新副本,這可能會導(dǎo)致大型數(shù)組出現(xiàn)性能問題。
此外, findIndex() 仍然無法在反轉(zhuǎn)數(shù)組上工作,因為反轉(zhuǎn)元素也意味著更改它們在原始數(shù)組中的索引。要獲得原始索引,我們需要執(zhí)行額外的計算,這意味著編寫更多代碼。
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
這段代碼更短,更易讀。最重要的是,它會產(chǎn)生正確的結(jié)果。
結(jié)論
所以我們已經(jīng)看到了 ES13 為 JavaScript 帶來的最新特性,使用它們來提高我們作為開發(fā)人員的工作效率,并以更簡潔和清晰的方式編寫更簡潔的代碼。如果你覺得我這篇文章對你有用的話,請記得點贊我,關(guān)注我,并將它分享給你身邊的朋友,也許能夠幫助到他。
作者:楊小愛
歡迎關(guān)注微信公眾號 :深圳灣碼農(nóng)