ES2022是什么?它的新特性又是什么呢?
ES2022是即将在2022年发布的JavaScript新特性。
ES2022的新特性有哪些?是谁来决定要发布哪些特性呢?这些特性又是谁发布的?
想要了解接着往下看,首先要介绍一下ECMA和TC39
ECMA & TC39
ECMA是一个国际化的组织,像ISO,IETE,W3C这种。
TC39是ECMA International标准化组织旗下的技术委员会的一员,负责管理着ECMAScript语言和标准化API
ECMAScript的缩写就是ES,我们熟悉的ES6,ES7,ES8等就是这个缩写加版本号来的。
ECMAScript语言和标准化API又可以分为两个标准:
- 第一个是ECMA-262标准,它包含了语言的语法和核心的API
- 另一个标准ECMA-402则包含一些国际化的API,提供给ECMAScript核心API选择性支持。
TC39
TC39成员的组成是由一些大型网站、学术研究者以及OpenJS基金会。以及社区代表如Babel、nodejs社区。还有一些突出贡献者,对某一议题有特殊贡献的人。
委员会成员每两个月开会讨论一次提案,由于疫情会在线上举办。还有GitHub和论坛,GitHub是完成大部分开发和决定的地方,论坛则是提案更早期讨论发起的地方。
开会主要形式是讨论,目的是为了达成一致,没有投票。
由于委员会由多样化的人组成,代表了不同的利益群体。TC39委员会的工作就是满足大家的需求和目标。
TC39的5个阶段
从想法到最终应用提案要经历5个阶段
- 0 Strawman稻草人: 仅有一个想法
- 1 Proposal提案:向委员会讲解和介绍概述解决方案,提出潜在困难。到此只是表明是一个值得讨论的议题且愿意继续讨论。
- 2 Draft草案:需讨论具体的语法语义的细节。提供具体的解决方法。
- 3 Candidate候选:需要接收具体实现者和用户们的反馈。
- 4 finished结束:新特性通过具体的测试,代表可以被大家使用。提案的标准和规范也会进入到主要的标准规范中。
近年来的版本
全称 | 发布年份 | 缩写 / 简称 |
---|---|---|
ECMAScript 2015 | 2015 | ES2015 / ES6 |
ECMAScript 2016 | 2016 | ES2016 / ES7 |
ECMAScript 2017 | 2017 | ES2017 / ES8 |
ECMAScript 2018 | 2018 | ES2018 / ES9 |
ECMAScript 2019 | 2019 | ES2019 / ES10 |
ECMAScript 2020 | 2020 | ES2020 / ES11 |
ECMAScript 2021 | 2021 | ES2021 / ES12 |
ECMAScript 2022 | 2022 | ES2022 / ES13 |
从ES2015开始往后的每一年发布一个版本,更新还是很频繁的。
ES2022特性
首先看下都有哪些新特性:
- Class Fields
- RegExp Math Indices
- Top-level await
- Ergonomic brand checks for Private Fields
- .at()
- Object.hasOwn()
- Class Static Block
接下来我会一一介绍,如何使用这些新特性。
请打开浏览器控制台(Mac快捷键: cmd+option+i),如下示例都可以在Chrome浏览器控制台执行。
Class Fields
在ES6提出class类之后,类仅支持公有变量,如果想要定义私有变量,约定在属性前面加下划线,但是此变量依然可以从外部访问到。如下ES6示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ButtonToggle {
constructor(){
// 公共属性
this.color = 'green'
// 定义私有属性
this._value = true
}
toggle() {
this.value = !this.value
}
static startFun () {
console.log('static 方法');
}
}
调用访问得到的结果如下,所以之前是没有私有变量的。
1
2
3
4
5
6
7
8
9
const button = new ButtonToggle();
console.log(button.color); // green
// 私有变量也可以访问
button._value = false;
console.log(button._value); // false
// 访问静态方法
ButtonToggle.startFun(); // static 方法
而在ES2022中提案版本中,包括了私有变量、私有方法、静态私有变量、静态私有方法四个,都安排上了。
设置私有属性的方法是在变量前面加上一个修饰符#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ButtonDemo {
// 公有变量
color = 'green';
// 私有变量
#value = true;
// 静态私有变量
static #private_static_field = 2
// 私有方法
#privateMethod () {
console.log('this is a private method');
}
// 静态私有方法
static #toggle() {
console.log('hello toggle');
}
publicFunc () {
this.#privateMethod();
console.log(ButtonDemo.#private_static_field, this.#value);
ButtonDemo.#toggle();
}
}
RegExp Math Indices
以前的正则表达式后面可以加i
和g
去修饰。i
代表忽略大小写,g
代表全局匹配,现在又增加了d
。
在正则后面添加d
匹配,则会返回匹配字符串的索引位置,通过indices
字段拿到。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const re1 = /a+(?<Z>z)?/d;
// indices are relative to start of the input string:
const s1 = "xaaaz";
const m1 = re1.exec(s1);
m1.indices[0][0] === 1;
m1.indices[0][1] === 5;
console.log(s1.slice(...m1.indices[0]) === "aaaz"); // true
m1.indices[1][0] === 4;
m1.indices[1][1] === 5;
console.log(s1.slice(...m1.indices[1]) === "z"); // true
m1.indices.groups["Z"][0] === 4;
m1.indices.groups["Z"][1] === 5;
console.log(s1.slice(...m1.indices.groups["Z"]) === "z"); // true
// capture groups that are not matched return `undefined`:
const m2 = re1.exec("xaaay");
console.log(m2.indices[1] === undefined); // true
console.log(m2.indices.groups["Z"] === undefined); // true
控制台打印m1如下:
仔细查看发现m1下面多了一个indices这个属性,里面返回是是匹配到的字符的索引,是一个数组。
是否还注意到有个groups
,返回groups是因为正则里面包含了(?<Z>z)
,这里的意思是,如果匹配到z
就把匹配到的字符放到z
属性里,具体查看Groups and ranges
Top-level await
顶层await。以前我们使用await必须要和async配合一起使用。
现在await将不受async的限制,如下在全局作用域使用import
的异步加载方式。
1
2
3
4
5
6
let jQuery;
try {
jQuery = await import('https://cdn-a.com/jQuery');
} catch {
jQuery = await import('https://cdn-b.com/jQuery');
}
还可以这样使用,是不是很方便。
1
const connection = await dbConnector();
Ergonomic brand checks for Private Fields
支持使用in去判断私有属性在对象里面存不存在
1
2
3
4
5
6
7
8
9
10
11
12
class C {
#brand;
static isC(obj) {
try {
obj.#brand;
return true;
} catch {
return false;
}
}
}
如上按照之前的方式判断,也可以满足情况,但会非常笨拙。
使用in
来解决此问题
1
2
3
4
5
6
7
8
9
10
11
class C {
#brand;
#method() {}
get #getter() {}
static isC(obj) {
return #brand in obj && #method in obj && #getter in obj;
}
}
.at()
取数组索引,传参为正正序取,传参为负从后向前取
1
2
3
4
var test = ['a', 'b', 'c']
test.at(1) // b
test.at(-1) // c
test.at(-2) // b
同样的也支持字符串,根据索引取值
1
2
3
4
var str = 'abc'
str.at(1) // b
str.at(-1) // c
str.at(-2) // b
Object.hasOwn()
提倡使用Object.hasOwn()
这个方法,来代替Object.prototype.hasOwnProperty
。
在之前我们怎么判断一个对象呢?有两个小问题
Object.create(null)
Object.create(null)
创建一个对象,但这个对象并不是继承Object.prototype
,所以使用下面的方法判断会抛出异常。
1
2
Object.create(null).hasOwnProperty("foo")
// Uncaught TypeError: Object.create(...).hasOwnProperty is not a function
重定义hasOwnProperty()
使用时不确定是不是访问的是原生的属性,有可能是重定义的就会出现如下错误。
1
2
3
4
5
6
7
8
let object = {
hasOwnProperty() {
throw new Error("gotcha!")
}
}
object.hasOwnProperty("foo")
// Uncaught Error: gotcha!
所以有了下面的方法,这种写法应该很普遍,来判断一个对象是不是它自己的。
1
2
3
4
5
let hasOwnProperty = Object.prototype.hasOwnProperty
if (hasOwnProperty.call(object, "foo")) {
console.log("has property foo")
}
我们在自己项目中这么写,有很多库也出了这样的方法来判断,于是乎大家的需求,委员会就考虑安排上了。于是就有了hasOwn
方法,让使用起来更简单。
1
2
3
if (Object.hasOwn(object, "foo")) {
console.log("has property foo")
}
Class Static Block(类静态成员初始化块)
在此以前,我们初始化类的静态成员变量只能在定义的时候去做,不能放到构造函数里面。
现在可以在类的内部开辟一个专门为静态成员初始化的作用域,在一些比较复杂的场景很适用.
class static block语法
1
2
3
4
5
class C {
static {
// statements
}
}
那一般会怎么用呢?如下在没有类静态成员初始化块之前,我们可能用一个工具函数去初始化静态成员:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// without static blocks:
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 = ...;
}
使用静态块,是不是优雅了不少。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// with static blocks:
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 = ...;
}
}
}
想要更好的理解,如下举个🌰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Translator {
static translations = {
yes: 'ja',
no: 'nein',
maybe: 'vielleicht',
};
static englishWords = [];
static germanWords = [];
static _ = initializeTranslator(); // (A)
}
// 初始化函数
function initializeTranslator() {
for (const [english, german] of Object.entries(Translator.translations)) {
Translator.englishWords.push(english);
Translator.germanWords.push(german);
}
}
一个类想要同时初始化创建两个静态变量,只能引入外部函数A行。
但这个代码还有两个问题
- 调用initializeTranslator()这个函数是额外的步骤,必须要等到类初始化之后执行,得通过一个变通的方案来执行,又会引入一些其他代码。
- 而且是initializeTranslator()函数内部,不能访问类的私有数据。
如果使用类静态初始化块,就会清晰很多.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Translator {
static translations = {
yes: 'ja',
no: 'nein',
maybe: 'vielleicht',
};
static englishWords = [];
static germanWords = [];
// 静态块
static { // (A)
// 静态成员初始化
for (const [english, german] of Object.entries(this.translations)) {
this.englishWords.push(english);
this.germanWords.push(german);
}
console.log(this.englishWords, this.germanWords);
}
}
还可以使用多个static block
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyClass {
static field1 = console.log('field1 called');
static {
console.log('Class static block #1 called');
}
static field2 = console.log('field2 called');
static {
console.log('Class static block #2 called');
}
}
/*
> "field1 called"
> "Class static block #1 called"
> "field2 called"
> "Class static block #2 called"
*/
现有的面向对象语言Java、C#都有相应的方法供静态成员初始化。JavaScript中类添加类静态块,让类的静态特性更趋完善,使用起来也会更优雅方便。
以上就是所有新特性,欢迎补充交流。:)
Class static initialization blocks