ES13 (ES2022)
ES2022(ES13)引入了一系列实用的新特性,包括数组和字符串的at()方法、类字段和私有成员、错误原因追踪、顶层await、更安全的对象属性检查以及正则表达式匹配索引。这些更新使JavaScript代码更加简洁、安全,并提供了更强大的类封装和错误处理能力。
ES2022(ES13)的新特性涵盖了数组、类、错误处理、异步操作等多个领域:
- at( ) 方法
- 类字段和私有成员
- Error.cause
- Top-Level await
- Object.hasOwn( )
- RegExp Match Indices
1. at( ) 方法
at( ) 方法为数组、字符串等可索引的数据类型提供了一种更简洁、安全的方式来访问元素,支持正索引和负索引。负索引从末尾开始计数。
const arr = [10, 20, 30, 40, 50];
console.log(arr.at(0));
console.log(arr.at(2));
console.log(arr.at(-1));
const str = "Hello";
console.log(str.at(0));
console.log(str.at(-1));
该API主要是解决防卫可索引数据类型的末尾元素的问题。
arr[arr.length - 1]; // 访问末尾元素
arr.at(-1);
当访问超出边界的索引时,at( ) 方法和普通索引访问方式一样,返回 undefined
console.log(arr[10]); // undefined
console.log(arr.at(10)); // undefined
2. 类字段和私有成员
先回顾一下以前书写类的方式:
class Person{
constructor(name, age){
this.name = name;
this.age = age;
}
sayHello(){ ... }
}
ES2022 允许开发者在类中直接定义字段:
class Person {
name = "张三";
age = 18;
sayHello() {
console.log(`Hello, I'm ${this.name}`);
}
}
const p = new Person();
p.sayHello();
p.name = "李四";
p.sayHello();
不过想要在实例化对象的时候做成员的初始化,就还是得借助 constructor 方法。
另外一个大的更新,就是原生支持私有成员了,在以前,想要实现私有成员的效果,得通过一些模拟才能实现:
function Person(name, age) {
// 私有属性
var _name = name;
var _age = age;
// 私有方法
function privateMethod() {
console.log("这是一个私有的方法");
}
this.getName = function () {
return _name;
};
this.getAge = function () {
return _age;
};
this.setName = function (name) {
_name = name;
};
this.setAge = function (age) {
_age = age;
};
this.method = function () {
privateMethod();
};
}
var p = new Person("张三", 20);
console.log(p.getName());
console.log(p.getAge());
p.method();
p.setName("李四");
p.setAge(22);
console.log(p.getName());
console.log(p.getAge());
到了 ES6 时期,虽然已经提供了 class 关键字,但是私有成员仍然得通过模拟的方式:
class Person {
// 私有属性
_name;
_age;
constructor(name, age) {
this._name = name;
this._age = age;
}
// 私有方法
_privateMethod() {
console.log("这是一个私有的方法");
}
get name() {
return this._name;
}
get age() {
return this._age;
}
set name(name) {
this._name = name;
}
set age(age) {
this._age = age;
}
method() {
this._privateMethod();
}
}
var p = new Person("张三", 20);
console.log(p.name);
console.log(p.age);
p.method();
p.name = "李四";
p.age = 22;
console.log(p.name);
console.log(p.age);
console.log(p._name);
ES2022 正式推出了原生私有成员的书写方式,通过一个 # 来表示该成员是私有成员:
class Person {
// 私有属性
#name;
#age;
constructor(name, age) {
this.#name = name;
this.#age = age;
}
// 私有方法
#privateMethod() {
console.log("这是一个私有的方法");
}
get name() {
return this.#name;
}
get age() {
return this.#age;
}
set name(name) {
this.#name = name;
}
set age(age) {
this.#age = age;
}
method() {
this.#privateMethod();
}
}
var p = new Person("张三", 20);
console.log(p.name);
console.log(p.age);
p.method();
p.name = "李四";
p.age = 22;
console.log(p.name);
console.log(p.age);
console.log(p.#name); // 报错
3. Error.cause
在 ES2022 之前,开发者通常手动传递或打印原始错误对象,作为错误上下文的一部分。那个时候经常需要对错误信息进行拼接。
传统处理方式:
try {
throw new Error("数据库连接失败");
} catch (err) {
console.log(`具体错误信息: ${err.message}`); // 手动拼接错误消息
}
ES2022 引入了 Error.cause 这个新特性,它为 Error 对象提供了一种标准化的方式来传递额外的上下文信息。通过 Error.cause 属性,开发者可以在抛出错误时传递一个"根本原因"(cause)对象或信息,用来解释导致该错误的更深层次原因。
语法:
new Error(message, { cause });
- message: 错误信息,描述错误的表面原因。
- cause: 详细描述导致该错误的根本原因,通常是另一个错误对象或一些有用的上下文信息。
基本示例:
try {
throw new Error("出错了", { cause: "因为网络原因" });
} catch (e) {
console.log(e.message);
console.log(e.cause);
}
除了基础的 Error,以下错误类型同样支持 cause 属性:
- TypeError:例如类型不匹配的错误。
- SyntaxError:语法错误。
- RangeError:值超出允许范围时抛出的错误。
- ReferenceError:访问未声明的变量时抛出的错误。
4. Top-Level await
ES2022 允许在模块的顶级作用域中使用 await,无需再将 await 包含在 async 函数内。这简化了处理异步模块加载的方式,尤其是在 ES 模块中。
const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const data = await response.json();
console.log(data);
5. Object.hasOwn( )
Object.prototype.hasOwnProperty( ) 是 JS 中传统用于检测对象是否具有某个自身属性的方式,但它存在几个使用上的问题:
-
重写问题:如果对象本身重写了 hasOwnProperty( ) 方法,那么直接使用 obj.hasOwnProperty( ) 会导致错误。
const obj = {
name: "张三",
age: 20,
hasOwnProperty() {
return "hello";
},
};
console.log(obj.hasOwnProperty("name")); // hello -
需要通过 Object.prototype 调用:为避免上述问题,开发者需要使用 Object.prototype.hasOwnProperty.call(obj, 'prop') 的形式,这显得过于冗长。
const obj = {
name: "张三",
age: 20,
hasOwnProperty() {
return "hello";
},
};
// console.log(obj.hasOwnProperty("name")); // hello
const result = Object.prototype.hasOwnProperty.call(obj, "name");
console.log(result); // true
为了简化操作并解决 hasOwnProperty( ) 的问题,Object.hasOwn( ) 被引入。它直接作为 Object 的静态方法,避免了继承链上的问题,并提供了更简洁的语法。
const obj = {
name: "张三",
age: 20,
hasOwnProperty() {
return "hello";
},
};
// console.log(obj.hasOwnProperty("name")); // hello
// const result = Object.prototype.hasOwnProperty.call(obj, "name");
const result = Object.hasOwn(obj, "name");
console.log(result); // true
6. RegExp Match Indices
RegExp Match Indices 特性为正则表达式的匹配结果提供了匹配项和捕获组的开始和结束索引。通过在正则表达式中使用 d 标志,可以获取到详细的匹配位置(indices),从而精确定位正则表达式匹配到的文本在原始字符串中的起始和结束位置。
在没有 d 标志的情况下,正则表达式的执行结果仅包含匹配的字符串及其捕获组,但无法直接获取匹配项的起始和结束索引。要获取匹配的起始索引只能通过手动计算或结合 exec( ) 的 index 属性,这会增加很多额外的复杂计算。通过引入 d 标志,正则表达式不仅会返回匹配的文本,还会返回一个 indices 数组,其中每个元素表示匹配文本或捕获组的开始和结束索引。
示例1:基本示例
const regex1 = /(foo)(bar)?/;
const regex2 = /(foo)(bar)?/d;
const str = "abfoobarcd";
console.log(regex1.exec(str));
console.log(regex2.exec(str));