ES2022新特性共有8条,并且当前全部处于 stage 4
阶段,预计将在今年六月成为标准。下面我们一起先睹为快!
1. Class Fields
Field declarations
在ES2022之前,给class定义一个字段,我们要在constructor里定义:
class X {
constructor() {
this.a = 123;
}
}
ES2022允许我们直接这么写:
class X {
a = 123;
}
Fields without initializers are set to undefined
公共字段和私有字段如果没有被初始化赋值,则会默认设置为undefined
class X {
a;
#b;
getB() {
console.log(this.#b);
}
}
x = new X();
x.a // undefined
x.getB() // undefined
对于多个class继承,如果一个字段在多个class上都有定义,那么会以最近的class定义为准
class A {
a = 1;
}
class B extends A {
a;
}
const b = new B();
b.a // undefined
class C {
a;
}
class D extends C {
a = 1;
}
const d = new D();
d.a // 1
Public fields created with Object.defineProperty
公共字段都是通过Object.defineProperty创造的。当某一个字段,get、set也同时存在时,TC39委员会经过漫长的讨论,最终决定用Object.defineProperty的get、set默认行为,而不是class里定义的get和set。
class A {
set x(value) { console.log(++value); }
get x() { return 'x' }
}
class B extends A {
x = 1;
}
const b = new B();
b.x; // 1 (并不会返回'x')
b.x = 2; // 控制台不会打印3
Private fields
在ES2022之前,并没有实际意义上的私有字段。大家形成一种默契,通常用下划线_开头的字段名来表示私有字段,但是这些字段还是可以手动更改的。
ES2022给我们提供了更加安全便捷的私有字段定义方法,就是以#开头命名的字段,都会被当成私有字段,在class外部是没办法直接读取、修改这些私有字段的。
class X {
#a = 123;
}
const x = new X();
x.#a // Uncaught SyntaxError: Private field '#a' must be declared in an enclosing class
2. RegExp Match Indices
正则表达式增加了一个/d
修饰符,当使用正则表达式的exec()
方法时,如果有/d
修饰符,那么结果会多返回一个indices属性,用来表示匹配的结果的在原字符串中的起始index值。
const re1 = /a+/d;
const s1 = "aaabbb";
const m1 = re1.exec(s1);
m1.indices[0] //[0, 3];
控制台打印结果
如果正则表达式中有具名捕获组,那么indices[1]
则表示捕获组的起始index值,indices.groups
同样记录了捕获组信息
const re1 = /a+(?<B>b+)/d;
const s1 = "aaabbbccc";
const m1 = re1.exec(s1);
m1.indices[1] //[3, 6];
m1.indices.groups //{ B: [3, 6] };
3. Top-level await
之前我们使用await
时,必须使用async
包裹起来,新的提案允许我们直接使用await
;
官方的例子:
// awaiting.mjs
import { process } from "./some-module.mjs";
export default (async () => {
const dynamic = await import(computedModuleSpecifier);
const data = await fetch(url);
const output = process(dynamic.default, data);
return { output };
})();
// usage.mjs
import promise from "./awaiting.mjs";
export default promise.then(({output}) => {
function outputPlusValue(value) { return output + value }
console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100), 1000);
return { outputPlusValue };
});
不使用顶层await
,awaiting.mjs export的是一个异步方法,其他文件引用后,代码逻辑要写到回调里。
// awaiting.mjs
import { process } from "./some-module.mjs";
const dynamic = import(computedModuleSpecifier);
const data = fetch(url);
export const output = process((await dynamic).default, await data);
// usage.mjs
import { output } from "./awaiting.mjs";
export function outputPlusValue(value) { return output + value }
console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100), 1000);
使用顶层await
,awaiting.mjs export的是最终结果,其他文件引用后就可以直接使用。
4. Ergonomic brand checks for Private Fields
私有字段检测,之前在判断一个对象里有没有某个私有字段,是比较麻烦的,因为在访问对象上一个不存在的私有属性时,会抛出异常。通常用try
/catch
确保方法不会报错。
class C {
#brand;
static hasBrand(obj) {
try {
obj.#brand;
return true;
} catch {
return false;
}
}
}
const c = new C();
const d = {};
C.hasBrand(c) // true
C.hasBrand(d) // false
ES2022新提案中,可以用in
操作符来判断对象中是否存在某个私有字段、私有方法或者getter。
class C {
#brand;
#method() {}
get #getter() {}
static isC(obj) {
return #brand in obj && #method in obj && #getter in obj;
}
}
const c = new C();
const d = {};
C.isC(c) // true
C.isC(d) // false
5. .at()
新增的取值方法,可作用于Array
, String
, TypedArray
。
.at()
接收一个参数,对于数组array=[1, 2, 3]
- 当参数是正数n时,结果跟直接获取数组的第n个元素
array[n]
一样 - 当参数是负数-n时,相当于倒取第n个元素,等同于
array[-n + array.length]
, - 当参数是其它值或者空时,直接返回数组第一个元素
const arr = [1, 2, 3, 4, 5];
arr.at(1) // 2
arr.at(-1) // 5
arr.at(-10) // undefined
arr.at('aaaa') // 1
arr.at() // 1
6. Accessible Object.prototype.hasOwnProperty
ES2022在Object原型上新增了Object.hasOwn
方法,判断对象本身是否存在某个属性,是Object.prototype.hasOwnProperty
的改良版本。
// object继承自对象{ foo: 'foo' }
const object = Object.create({ foo: 'foo' })
// 再给本身添加属性bar
object.bar = 'bar'
object.hasOwnProperty('foo') // false
object.hasOwnProperty('bar') // true
Object.hasOwn(object, 'foo') // false
Object.hasOwn(object, 'bar') // true
当使用Object.create(null)
创建一个没有继承 Object.prototype
的对象时,hasOwnProperty
方法无法直接使用;
const object = Object.create(null)
object.hasOwnProperty("foo") // Uncaught TypeError: Object.create(...).hasOwnProperty is not a function
Object.prototype.hasOwnProperty.call(object, 'foo') // false
Object.hasOwn(object, 'foo') // false
7. Class Static Block
以前,我们初始化类的静态变量只能在定义类的时候去做,而对于一些无法显示声明的变量,我们往往使用一个函数去初始化。我们也不能放到构造函数里面,因为在构造函数中,只能初始化具体某一实例的变量。
现在,我们可以在类内部开辟一个专门为静态成员初始化的代码块,我们称之为“静态代码块”
。
const doSomethingWith = (x) => {
return {y: x++, z: x**2}
}
// without static blocks:
class C {
static x = 3;
static y;
static z;
}
try {
const obj = doSomethingWith(C.x);
C.y = obj.y
C.z = obj.z;
}
catch {
C.y = 0;
C.z = 0;
}
// with static blocks:
class C {
static x = 3;
static y;
static z;
static {
try {
const obj = doSomethingWith(this.x);
this.y = obj.y;
this.z = obj.z;
}
catch {
this.y = 0;
this.z = 0;
}
}
}
静态块提供了对私有属性的特权访问权限
let getX;
class C {
#x
constructor(x) {
this.#x = x;
}
static {
// getX has privileged access to #x
getX = (obj) => obj.#x;
}
}
const c = new C('I am X');
getX(c) // I am X
8. Error Cause
之前,我们在封装错误信息时,比较繁琐,没有统一的字段的表示错误原因
async function doJob() {
const rawResource = await fetch('//domain/resource-a')
.catch(err => {
// How to wrap the error properly?
// 1. 字符串拼接错误原因
// throw new Error('Download raw resource failed: ' + err.message);
// 2. 用cause字段承接报错信息
// const wrapErr = new Error('Download raw resource failed');
// wrapErr.cause = err;
// throw wrapErr;
// 3. 二次封装Error
// 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,允许我们在实例化Error时,将错误原因以参数形式传入,省去了我们自己单独处理的成本。
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