Возможности ES2022: блоки статической инициализации класса

Источник: «ES2022 feature: class static initialization blocks»
Для настройки статической части класса у нас есть только статические поля. Предложение ECMAScript вводит статические блоки инициализации для классов, которые, грубо говоря, являются для статических классов тем же, чем конструкторы являются для экземпляров.

Предложение ECMAScript Блоки статической инициализации класса Рона Бактона/Ron Buckton находится на стадии 4 и планируется включить в ECMAScript 2022.

Для настройки экземпляра класса в JavaScript есть две конструкции:

Для настройки статической части класса у нас есть только статические поля. Предложение ECMAScript вводит статические блоки инициализации для классов, которые, грубо говоря, являются для статических классов тем же, чем конструкторы являются для экземпляров.

Зачем нужны статические блоки в классах

При настройке статических полей часто хорошо работает использование внешних функций:

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);
}
}

На этот раз возникает несколько вопросов:

С предложенным статическим блоком (строка 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);
}
}
}

Более сложный пример

Одним из способов реализации перечислений/enums в JavaScript — использование суперкласса 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'

Нам нужно собрать статические поля, чтобы мы могли перебирать ключи записей перечислений (строка 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'

Поддержка статических блоков класса в движках

Становится ли JavaScrip слишком похожим на Java и/или бардак

Это крошечная функция, которая не конкурирует с другими функциями. Мы уже можем запускать статический код через поля с обходным решением static _ = .... Статические блоки означают, что этот обходной путь больше не нужен.

Помимо этого, классы — это просто один из многих инструментов в арсенале программиста на JavaScript. Некоторые из нас его используют, некоторые нет, и есть множество альтернатив. Даже код JavaScript, который использует классы, часто также использует функции и имеет тенденцию быть легковесным.

Вывод

Статические блоки класса — относительно простая функция, дополняющая статические возможности класса. Грубо говоря, это статическая версия конструктора экземпляра. Это полезно, когда нужно настроить более одного статического поля.

Дополнительные материалы

Предыдущая Статья

Tailwind CSS v3.0: 10 лучших новых возможностей

Следующая Статья

JavaScript: Проверка формы с Constraint Validation API