对于类的静态部分的设置, 我们只有静态字段. ECMAScript 建议为类引入静态初始化块, 大致上, 它对静态类的作用就像构造函数对实例的作用.
Ron Buckton 提出的 ECMAScript 提案 "类静态初始化块" 已进入第四阶段, 计划纳入 ECMAScript 2022.
为了建立一个类的实例, 在 JavaScript 中有两个结构:
字段: 创建 (可选择初始化) 实例属性.
构造函数: 在 setup 完成之前执行的代码块.
对于类的静态部分的设置, 我们只有静态字段. ECMAScript 建议为类引入静态初始化块, 大致上, 它对静态类的作用就像构造函数对实例的作用.
1. 为什么我们需要类中的静态块?
在设置静态字段时, 使用外部函数通常也可以很好地工作:
- class Translator {
- static translations = {
- yes: 'ja',
- no: 'nein',
- maybe: 'vielleicht',
- };
- static englishWords = extractEnglish(this.translations);
- static germanWords = extractGerman(this.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)
- this.translations, this.englishWords, this.germanWords);
- }
- function initializeTranslator(translations, englishWords, germanWords) {
- for (const [english, german] of Object.entries(translations)) {
- englishWords.push(english);
- 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(this.translations)) {
- this.englishWords.push(english);
- this.germanWords.push(german);
- }
- }
- }
2. 一个更复杂的例子
在 JavaScript 中实现枚举的一种方法是通过带有辅助功能的超类 Enum
- class Enum {
- static collectStaticFields() {
- // Static methods are not enumerable and thus ignored
- 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 行), 静态块会更优雅.
3. 详情
静态块的具体内容相对来说是合乎逻辑的(相比之下, 实例成员的规则更为复杂):
每个类可以有一个以上的静态块.
静态块的执行是与静态字段初始化器的执行交错进行的.
超类的静态成员在子类的静态成员之前被执行.
下面的代码展示了这些规则:
- 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'
4. 在引擎中支持类静态块
- V8: unflagged in v9.4.146 (source)
- SpiderMonkey: behind a flag in v92, intent to ship unflagged in v93 (source)
- TypeScript: v4.4 (source)
5.JS 是否变得太像 Java 和 / 或一塌糊涂?
这是一个很小的功能, 不会与其他功能竞争. 我们已经可以通过 static _ = ... 的字段来运行静态代码. 静态块意味着这种变通方法不再需要了.
除此之外, 类只是 JavaScript 程序员腰带上的众多工具之一. 我们中的一些人使用它, 另一些人不使用它, 而且有许多替代方案. 即使是使用类的 JS 代码, 也经常使用函数, 而且往往是轻量级的.
6. 总结
类静态块是一个相对简单的功能, 它完善了类的静态功能. 粗略来说, 它是实例构造函数的静态版本. 它主要在我们需要设置一个以上的静态字段时有用.
作者: Dr. Axel Rauschmayer 译者: 前端小智
来源: http://developer.51cto.com/art/202111/692123.htm