- 原文地址:ES2022 feature: class static initialization blocks
- 原文作者:Dr. Axel Rauschmayer
- 译文出自:掘金翻译计划
Ron Buckton向ECMAScript提交的“Class static initialization blocks” 提案已经进入“阶段4”,预期将进入ECMAScript 2022。
在JavaScript中,要创建类的实例,有两种构造可以使用:
- 字段:创建(并可选地初始化)实例属性。
- 构造器:代码块,在实例创建完成之前执行。
至于创建类的静态部分,则只有静态字段。这里提到的ECMAScript提案给类引入了静态初始化块。简单地说,静态初始化块之于静态类,就相当于构造器之于实例。
目录:
为什么类中需要静态块?
在创建静态字段时,通常可以使用外部函数:
class Translator {
static translations = {
yes: 'ja',
no: 'nein',
maybe: 'vielleicht',
};
static englishWords = extractEnglish(translations);
static germanWords = extractGerman(translations);
}
function extractEnglish(translations) {
return Object.keys(translations);
}
function extractGerman(translations) {
return Object.values(translations);
}
在这里使用外部函数 extractEnglish()和 extractGerman() 之所以行得通,是因为可以看到它们在类的内部被调用,而且它们与类是完全独立的。
如果想同时创建两个静态字段,代码看起来就没那么清晰了:
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);
}
}
这次的代码有几个问题:
- 调用
initializeTranslator()是额外的步骤,这个步骤要么必须等到类初始化之后执行,要么就得通过一个变通方案来执行(A行)。 initializeTranslator()不能访问Translator的私有数据。
使用提案建议的静态块(A行),代码会更容易理解。
class Translator {
static translations = {
yes: 'ja',
no: 'nein',
maybe: 'vielleicht',
};
static englishWords = [];
static germanWords = [];
static { // (A)
for (const [english, german] of Object.entries(translations)) {
this.englishWords.push(english);
this.germanWords.push(german);
}
}
}
一个更复杂的例子
在JavaScript中要实现枚举,一种方式是通过超类Enum,外加一些辅助(更完善的实现可以参考 这个库enumify ):
class Enum {
static collectStaticFields() {
// 静态方法不能枚举,因此忽略
this.enumKeys = Object.keys(this);
}
}
class ColorEnum extends Enum {
static red = Symbol('red');
static green = Symbol('green');
static blue = Symbol('blue');
static _ = this.collectStaticFields(); // (A)
static logColors() {
for (const enumKey of this.enumKeys) { // (B)
console.log(enumKey);
}
}
}
ColorEnum.logColors();
// Output:
// 'red'
// 'green'
// 'blue'
我们想收集静态字段,因而可以迭代枚举条目的键(B行)。这一步是在创建所有静态字段之后执行的。这里同样也用到了一个变通方案(A行)。同样,使用静态块会让代码更清晰。
细节
静态块的细节相对符合逻辑(相较于针对实例成员更复杂的规则而言):
- 每个类可以有多个静态块。
- 静态块的执行与静态字段初始化程序的执行是交错的。
- 超类的静态成员在子类静态成员之前执行。
以下代码演示了这些规则:
class SuperClass {
static superField1 = console.log('superField1');
static {
assert.equal(this, SuperClass);
console.log('static block 1 SuperClass');
}
static superField2 = console.log('superField2');
static {
console.log('static block 2 SuperClass');
}
}
class SubClass extends SuperClass {
static subField1 = console.log('subField1');
static {
assert.equal(this, SubClass);
console.log('static block 1 SubClass');
}
static subField2 = console.log('subField2');
static {
console.log('static block 2 SubClass');
}
}
// Output:
// 'superField1'
// 'static block 1 SuperClass'
// 'superField2'
// 'static block 2 SuperClass'
// 'subField1'
// 'static block 1 SubClass'
// 'subField2'
// 'static block 2 SubClass'
支持类静态块的引擎
结论
类静态块是个相对简单的特性,让类的静态特性更趋完善。粗略地讲,它是实例构造器的静态版。其主要用途是创建多个静态字段。