ES2022 功能:类静态初始化块

788 阅读3分钟

ES2022 功能:类静态初始化块

罗恩·巴克顿提出的ECMAScript提案"类静态初始化块"处于第4阶段,并计划列入ECMAScript 2022。

对于设置一个类实例,我们在 JavaScript 中有两个构件:

  • 字段:创建(并可选初始化)实例属性。
  • 构造器:在设置完成之前执行的代码块。

要设置类的静态部分,我们只有静态字段。ECMAScript 提案为类引入静态初始化块,大致来说就是该区块用于静态类,构造器是实例。


表的内容:

为什么我们需要课堂中的静态块?

更复杂的示例

细节

支持类静态块的引擎

JavaScript变得很像Java和/或一样糟?

结论


为什么我们在类中需要静态块?

设置静态字段时,使用外部函数通常效果好一些:

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()是一个额外的步骤,要么必须执行课外,在创建它。或者通过解决方法(line A)执行。
  • initializeTranslator()无法访问 Translator

通过提出的静态块(line 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);
    }
  }
}

更复杂的示例

在 JavaScript 中实现 enums 的一种方法是通过具有辅助功能的超级类Enum(参阅enumify,以便更有力地实现此理念)

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'

我们需要收集静态字段,以便我们可以遍历枚举项的键(line B)。这是创建所有静态字段后的最后一步。我们再次使用解决方法(line 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'

支持类静态块的引擎

  • V8: unflagged in v9.4.146 (source)
  • SpiderMonkey: behind a flag in v92, intent to ship unflagged in v93 (source)
  • TypeScript: v4.4 (source)

JavaScript变得很像Java和/或一样糟?

这是一个不与其他功能竞争的小功能。我们已经可以通过 static _ = ...的解决方案运行静态代码。静态块意味着此解决方法不再必要。

除此之外,类只是 JavaScript 程序员腰带上的众多工具之一。我们中的一些人使用它,一些人没有,有很多选择。即使是使用类的 JavaScript 代码也经常使用功能,并且趋向于轻量级。

结论

类静态块是一个相对简单的功能,可以四舍五入类的静态功能。大致来说,它是实例构造器的静态版本。当我们必须设置多个静态字段时,它非常有用。