ES13(ES2022)中11个新特性解读
与其他大多数编程语言一样,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 = 'We';
_lastName = 'Gon'; get name() {
return `${this._firstName} ${this._lastName}`;
}
}
const person = new Person();
// 仍可以访问私有成员
console.log(person.name); // We Gon
// 在Class类外部访问
console.log(person._firstName); // We
console.log(person._lastName); // Gon
// 仍可以被更改
person._firstName = 'Ghosts';
person._lastName = 'Becker';
console.log(person.name); // Ghosts Becker
在ES13中,我们现在可以向类添加私有字段和成员,方法是在类前面加上标签( #
),试图从类外部访问它们将导致错误:
class Person {
#firstName = 'We';
#lastName = 'Gon'; get name() {
return `${this.#firstName} ${this.#lastName}`;
}
}
const person = new Person();
// Uncaught SyntaxError:Private field '#firstName' must be declared in an enclosing class
console.log(person.name);
// 属性 "#lastName" 在类 "Person" 外部不可访问,因为它具有专用标识符。
console.log(person.#firstName); // We
console.log(person.#lastName); // Gon
在VsCode编辑器会提示:属性 "#lastName" 在类 "Person" 外部不可访问,因为它具有专用标识符。
请注意,这里抛出的错误是一个语法错误,发生在编译时,因此不能运行代码的任何部分。
3. await运算符
在JavaScript中,await
操作符用于暂停执行,直到Promise
被解决 (完成 / 拒绝)。
以前,只能在async
函数中使用await
操作符——该函数使用async
关键字声明。无法在全局范围内直接使用await
。
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 )的语言中的静态构造函数。
一个类在其类体中可以有任意数量的 static {}
初始化块,静态字段一起被初始化,然后按照声明的顺序执行。我们可以使用static
块中的 super
属性来访问父类的属性。
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
元素,通常都比较简单。
const arr = ['a', 'b', 'c', 'd'];
console.log(arr[1]); // b
However, we have to use an index of arr.length - N
if we want to access the N
th item from the end of the array with square brackets.
但是, 如果我们想用方括号从数组末尾访问第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
元素,只需向at()
传递一个负值 -N
。
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()
方法。Array.prototype.at
跟 String.prototype.at
用法基本一致。
TypedArray
指的是一类指定元素类型的数组,而不是实际的数组类型。(ES6+)
const str = 'Coding Beauty';
console.log(str.at(-1)); // y
console.log(str.at(-2)); // tconst typedArray = new Uint8Array([16, 32, 48, 64]);
console.log(typedArray.at(-1)); // 64
console.log(typedArray.at(-2)); // 48
8. 正则表达式匹配索引
这个新特性允许我们指定我们想要获得给定字符串中 RegExp
对象匹配的开始和结束索引。
以前,我们只能在字符串中获得regex匹配的起始索引。
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
regex标志来获取匹配开始和结束的两个索引。
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
属性,其中包含起始和结束索引。
还可以添加命名组,如 ?<Z>
:
const re1 = /a+(?<Z>z)?/d;
const s1 = "xaaaz";
const m1 = re1.exec(s1);
console.log(m1.groups.Z); // 'z'
console.log(m1.indices.groups.Z); // [4, 5]
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;
// 重写方法 默认返回false
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
上调用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
现在不需要这样做了,在ES13 可以使用新的内置的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. 错误原因(Error 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()
方法来查找数组中通过指定条件的元素。类似地,我们可以使用Array.findIndex()
来查找此元素的索引。虽然find()
和 findIndex()
都从数组的第一个元素开始搜索,但在某些情况下,从最后一个元素开始搜索会更合适。
在某些情况下,从最后一个元素中查找可能会获得更好的性能。例如,这里我们使用find()
和findIndex()
方法 使用条件value === 'y'
来获取数组中的项:
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];
// 结果是14而不是10
const lastEven = nums.find((value) => value % 2 === 0);
// 结果是1而不是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];
// 使用扩展语法复制整个数组,然后调用 reverse() 方法
const reversed = [...nums].reverse();
// 正确地给10
const lastEven = reversed.find((value) => value % 2 === 0);
// 得到1,而不是4
const reversedIndex = reversed.findIndex((value) => value % 2 === 0);
// 需要重新计算得到原始索引
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带来的最新特性。使用它们可以提高开发人员的工作效率,并以更简洁和清晰的方式编写更通俗易懂的代码。
翻译:Gonwe