ES2022 新特性共有8条,当前全部处于 stage 4
定案阶段,预计将在今年六月成为标准。ECMAScript 当前的全部提案,可以在 TC39 的官网查看。下面介绍下ES2022的提案。
Class Fields
这是一个与类
相关特性的集合。从 ES6 Class 开始,我们就可以在 JavaScript 中使用 class
关键字书写传统面向对象编程范式的代码了。但类
的语法一直不太完善,这个提案就是对类
语法 的一个丰富。
Private Instance Fields 私有实例字段
之前类的实例中的字段都是可以被访问的,不安全且存在实例污染。现在我们通过符号#
来声明私有属性。现在当我们通过实例访问私有属性时,静态检查时直接报错。
class Counter {
#NUM = 0
NUM = 1
}
const counter = new Counter()
console.log(counter.#NUM) // VM723:1 Uncaught SyntaxError: Private field '#NUM' must be declared in an enclosing class
console.log(counter.NUM) // 1
复制代码
Private instance methods and accessors 私有实例方法和访问器
同样的,ES2022
也支持私有实例方法和访问器,也是使用符号#
来声明。
class Counter {
NUM = 1
//私有访问器
get #getNum() {
return this.NUM
}
//非私有访问器
get getNum() {
return this.NUM
}
//私有实例方法
#getStr() {
return '私有实例方法'
}
//非私有实例方法
getStr() {
return '非私有实例方法'
}
}
const counter = new Counter()
console.log(counter.#getNum) // Private field '#getNum' must be declared in an enclosing class
console.log(counter.getNum) // 1
console.log(counter.#getStr()) // Private field '#getStr' must be declared in an enclosing class
console.log(counter.getStr()) //非私有实例方法
复制代码
在尝试访问私有访问器#getNum
和私有方法#getStr()
时,发生报错提示。
Class Public Instance Fields 类公共实例字段
在 ES2022 之前,我们想定义一个实例的默认值,只能通过 constructor 定义:
class Counter {
constructor() {
this.num = 0;
}
}
复制代码
现在我们可以这样:
class Counter {
num = 0;
}
复制代码
Static class fields and methods 静态类字段和方法
使用符号static
来声明静态字段和方法,静态类字段和方法属于整个类,并非某一具体的实例。
class Counter {
// 静态字段
static NUM = 1
// 静态方法
static getNUM() {
return this.NUM
}
}
//只能通过类来直接访问类的静态字段和静态方法
console.log(Counter.NUM) //1
console.log(Counter.getNUM()) // 1
// 无法通过实例访问类的静态字段和静态方法
const counter = new Counter()
console.log(counter.NUM) // undefined
console.log(counter.getNUM()) // TypeError: counter.getNUM is not a function
复制代码
private static fields and methods 静态类字段和私有静态方法
class Counter {
// 静态私有字段
static #NUM = 1
// 静态公有字段
static NUM = 1
// 静态私有方法
static #getNUM() {
return this.#NUM
}
// 静态公有方法
static getNUM() {
return this.#NUM
}
}
//类只能访问静态公有字段和静态公有方法,无法访问静态私有字段和静态私有方法
console.log(Counter.NUM) //1
console.log(Counter.#NUM) //Private field '#NUM' must be declared in an enclosing class
console.log(Counter.getNUM()) // 1
console.log(Counter.#getNUM()) // Private field '#getNUM' must be declared in an enclosing class
// 实例无法访问静态字段和静态方法,公有私有都不行
const counter = new Counter()
console.log(counter.NUM) // undefined
console.log(counter.#NUM) // Private field '#NUM' must be declared in an enclosing class
console.log(counter.getNUM()) // TypeError: counter.getNUM is not a function
console.log(counter.#getNUM()) // TypeError: counter.getNUM is not a function
复制代码
上述提到关于类的几个新特性,在其他面向对象的语言中已经十分常见。
RegExp Match Indices
正则的exec()
方法是在一个指定字符串中执行一个搜索匹配,会返回一个结果数组或 null
。以前的正则表达式有 i
和 g
两个修饰符,i
代表忽略大小写,g
代表全局匹配,现在又加了一个 d
修饰符。
加上d
后,exec方法返回的结果数组就会多出一个 indices
属性,用来返回匹配结果的开始索引和结束索引。
const str1 = 'foo bar foo';
const regex1 = new RegExp('foo', 'gd');
//加上/d修饰符,存在indices属性
console.log(regex1.exec(str1).indices[0]); // Output: Array [0, 3]
console.log(regex1.exec(str1).indices[0]); // Output: Array [8, 11]
const str2 = 'foo bar foo';
const regex2 = new RegExp('foo');
//没加/d修饰符,不存在indices属性
console.log(regex2.exec(str2).indices); // Output: undefined
复制代码
Top-level await
以前我们使用 await
时,必须要放到 async
函数里。这就限制了一些场景,比如我们在全局作用域使用 import
的异步加载方式,亦或者初始化资源的时候。而这个特性就是为这些场景提供了便利:
// 不使用顶层await时
import { process } from "./some-module.mjs";
let output;
// 通过async和立即执行函数,解决该问题
(async () => {
const dynamic = await import(computedModuleSpecifier);
const data = await fetch(url);
output = process(dynamic.default, data);
})();
export { output };
复制代码
// 使用顶层await时,不再需要额外创建async函数
import { process } from "./some-module.mjs";
const dynamic = import(computedModuleSpecifier);
const data = fetch(url);
export const output = process((await dynamic).default, await data);
复制代码
Ergonomic brand checks for Private Fields
在新的提案中,可以使用in
关键字用来检测某一实例的类中是否含有私有字段,私有字段包含私有属性,私有函数以及私有访问器。没有该方法,很难从实例中判断当前类中是否含有私有字段。
class Demo {
#foo
#method() {}
get #getter() {}
//检测是否含有私有字段
static hasPrivateFields(obj) {
return #foo in obj && #method in obj && #getter in obj
}
}
const obj = new Demo()
console.log(Demo.hasPrivateFields(obj)) //true
复制代码
.at()
在之前,我们如果想获取数组的最后一个元素,只能使用arr[arr.length-1]
,无法使用arr[-1]
,因为[]
语法适用于所有对象,[-1]
实际上是获取具有键为“-1”
的对象的属性,而非对象的最后一个属性。
const arr = [1, 2, 3]
console.log(arr[0]) // 1
console.log(arr[arr.length - 1]) // 3
console.log(arr[-1])//undefined
复制代码
在新的提案中,at()
解决了该问题。现在我们可以使用.at()
取数组的倒数元素,这对动态数组尤其好用。
const arr = [1, 2, 3]
console.log(arr[0]) // 1
console.log(arr[arr.length - 1]) // 3
console.log(arr.at(-1)) // 3
//i乘以2后,取最后一个元素
console.log(arr.map((i) => (i = i * 2)).at(-1))// 6
复制代码
Accessible Object.prototype.hasOwnProperty
ES2022在Object原型上新增了hasOwn()
方法,使用这个方法能够判断某个属性是对象本身的属性,还是原型链上的属性,非常好用。
// object继承自对象{ foo: 'foo' }
const object = Object.create({ foo: 'foo' })
// 再给本身添加属性bar
object.bar = 'bar'
// 遍历object对象上的属性
for (const key in object) {
//只获取对象本身上的属性
if (Object.hasOwn(object, key)) {
console.log(key) // bar
}
}
// 遍历object对象上的所有属性,含原型链上的属性
for (const key in object) {
console.log(key) //bar foo
}
复制代码
Class Static Block
以前,我们初始化类的静态变量只能在定义类的时候去做,而对于一些无法显示声明的变量,我们往往使用一个函数去初始化。我们也不能放到构造函数里面,因为在构造函数中,只能初始化具体某一实例的变量。
现在,我们可以在类内部开辟一个专门为静态成员初始化的代码块,我们称之为“静态代码块”
。
// 不使用静态代码块的时候:
class C {
static x = ...;
static y;
static z;
}
try {
const obj = doSomethingWith(C.x);
C.y = obj.y
C.z = obj.z;
}
catch {
C.y = ...;
C.z = ...;
}
// 使用静态代码块的时候:
class C {
static x = ...;
static y;
static z;
static {
try {
const obj = doSomethingWith(this.x);
this.y = obj.y;
this.z = obj.z;
}
catch {
this.y = ...;
this.z = ...;
}
}
}
复制代码
Error Cause
在之前我们必须做很多才能为捕获的错误增加上下文信息。此外,对于使用哪个属性表示错误原因,没有达成共识,这就使得开发人员无法准确的了解错误原因。
async function doJob() {
const rawResource = await fetch('//domain/resource-a')
.catch(err => {
// 我们如何封装err信息?
// 1. throw new Error('Download raw resource failed: ' + err.message);
// 2. const wrapErr = new Error('Download raw resource failed');
// wrapErr.cause = err;
// throw wrapErr;
// 3. class CustomError extends Error {
// constructor(msg, cause) {
// super(msg);
// this.cause = cause;
// }
// }
// throw new CustomError('Download raw resource failed', err);
})
const jobResult = doComputationalHeavyJob(rawResource);
await fetch('//domain/upload', { method: 'POST', body: jobResult });
}
await doJob(); // => TypeError: Failed to fetch
复制代码
现在 Error
提供的解决方案是向构造函数中添加一个附加选项参数cause
,cause
参数用于描述错误原因,其值将作为属性分配给错误实例。因此,可以将错误链接起来,而不必再繁琐的包装错误的形式。
async function doJob() {
const rawResource = await fetch('//domain/resource-a')
.catch(err => {
throw new Error('Download raw resource failed', { cause: err });
});
const jobResult = doComputationalHeavyJob(rawResource);
await fetch('//domain/upload', { method: 'POST', body: jobResult })
.catch(err => {
throw new Error('Upload job result failed', { cause: err });
});
}
try {
await doJob();
} catch (e) {
console.log(e);
console.log('Caused by', e.cause);
}
// Error: Upload job result failed
// Caused by TypeError: Failed to fetch
复制代码
以上就是ES2022的全部特性了。
参考链接
如有建议和问题,欢迎交流。