Статические поля и методы классов

Статические поля в классах JavaScript представляют собой свойства, принадлежащие самому классу, а не его экземплярам. В SWC (Speedy Web Compiler) поддержка статических полей реализуется через трансформацию синтаксиса в совместимый ES5/ES2015 код, что особенно важно при работе в средах без нативной поддержки class fields.

Базовое объявление статического поля:

class User {
  static role = "guest";
}

После трансформации SWC подобный код может быть преобразован в форму, эквивалентную присваиванию свойства классу:

class User {}

User.role = "guest";

Особенности трансформации статических полей

SWC обрабатывает статические поля на этапе компиляции, перемещая их из тела класса в присваивания после определения конструктора. Это обеспечивает:

  • совместимость с ранними версиями JavaScript;
  • корректную инициализацию после объявления класса;
  • сохранение семантики выполнения сверху вниз.

При наличии нескольких статических полей порядок их инициализации сохраняется:

class Config {
  static host = "localhost";
  static port = 8080;
}

Трансформация:

class Config {}

Config.host = "localhost";
Config.port = 8080;

Статические поля с вычисляемыми значениями

SWC поддерживает вычисляемые выражения:

class MathConfig {
  static base = 10;
  static doubleBase = this.base * 2;
}

После трансформации доступ к this в статическом контексте корректируется на имя класса:

class MathConfig {}

MathConfig.base = 10;
MathConfig.doubleBase = MathConfig.base * 2;

Такое поведение критично для сохранения семантики, поскольку внутри статической инициализации this указывает на сам класс.


Статические методы классов

Статические методы принадлежат классу и не требуют создания экземпляра для вызова. SWC транслирует их в обычные свойства функции-конструктора.

Исходный код:

class Logger {
  static log(message) {
    console.log(message);
  }
}

После компиляции:

class Logger {}

Logger.log = function (message) {
  console.log(message);
};

Контекст выполнения статических методов

Внутри статических методов this указывает на сам класс:

class Service {
  static name = "API";

  static getName() {
    return this.name;
  }
}

SWC сохраняет это поведение, заменяя контекст на явную ссылку:

class Service {}

Service.name = "API";

Service.getName = function () {
  return Service.name;
};

Взаимодействие статических методов и полей

Статические методы часто используют статические поля для хранения конфигурации или состояния класса:

class App {
  static version = "1.0";

  static getVersion() {
    return this.version;
  }

  static bumpVersion() {
    const [major, minor] = this.version.split(".");
    this.version = `${major}.${Number(minor) + 1}`;
  }
}

SWC преобразует это в последовательное присваивание:

class App {}

App.version = "1.0";

App.getVersion = function () {
  return App.version;
};

App.bumpVersion = function () {
  const [major, minor] = App.version.split(".");
  App.version = `${major}.${Number(minor) + 1}`;
};

Приватные статические поля

SWC поддерживает приватные статические поля через трансформацию с использованием WeakMap или замыканий (в зависимости от конфигурации).

Исходный код:

class Counter {
  static 

  static increment() {
    this.#value++;
  }

  static getValue() {
    return this.#value;
  }
}

Типичная трансформация SWC:

var _value = new WeakMap();

class Counter {}

_value.set(Counter, 0);

Counter.increment = function () {
  _value.set(Counter, _value.get(Counter) + 1);
};

Counter.getValue = function () {
  return _value.get(Counter);
};

Ключевые аспекты:

  • приватность обеспечивается через внешнее хранилище;
  • доступ возможен только внутри скоупа модуля;
  • невозможность обращения извне сохраняется на уровне трансформации.

Статические блоки и их влияние на трансформацию

Хотя основная цель SWC — поддержка статических полей и методов, статические блоки инициализации также влияют на порядок выполнения:

class Database {
  static connection;

  static {
    this.connection = createConnection();
  }
}

SWC разворачивает статический блок в последовательный код инициализации:

class Database {}

Database.connection = undefined;

Database.connection = createConnection();

Если в классе присутствуют и поля, и статические блоки, SWC строго сохраняет порядок их объявления.


Наследование и статические члены

Статические поля и методы наследуются, что SWC учитывает при генерации кода:

class Base {
  static type = "base";

  static getType() {
    return this.type;
  }
}

class Derived extends Base {}

В результате трансформации:

class Base {}

Base.type = "base";

Base.getType = function () {
  return Base.type;
};

class Derived extends Base {}

При этом доступ через наследника сохраняется благодаря прототипной цепочке:

Derived.getType(); // "base"

Переопределение статических методов

SWC не изменяет семантику переопределения, но фиксирует вызовы через конкретный идентификатор класса:

class A {
  static run() {
    return "A";
  }
}

class B extends A {
  static run() {
    return "B";
  }
}

Трансформация:

class A {}

A.run = function () {
  return "A";
};

class B extends A {}

B.run = function () {
  return "B";
};

Вызовы остаются строго привязанными к конкретному классу, без динамического разрешения имени через super в статическом контексте.


Ограничения трансформации SWC

При работе со статическими полями и методами существуют особенности:

  • невозможность корректного сохранения некоторых сложных выражений this в статике без явной подстановки имени класса;
  • различия в поведении при minify и tree-shaking;
  • потенциальные различия между режимами loose и strict трансформации;
  • зависимость порядка инициализации от конфигурации компилятора.

В режиме loose SWC упрощает трансформации:

class A {
  static x = 1;
}

Может быть преобразовано максимально прямо:

class A {}
A.x = 1;

В более строгом режиме добавляются вспомогательные конструкции для точного соблюдения спецификации.


Связь с приватными методами и статикой

SWC поддерживает приватные статические методы аналогично полям:

class Auth {
  static #hash(password) {
    return password + "_hashed";
  }

  static encode(password) {
    return this.#hash(password);
  }
}

Трансформация:

var _hash = new WeakSet();

class Auth {}

_hash.add(Auth);

Auth.encode = function (password) {
  return password + "_hashed";
};

При этом внутренняя логика может различаться в зависимости от версии SWC и включённых трансформеров, но принцип изоляции остаётся неизменным.


Итоговые особенности поведения статических членов в SWC

  • статические поля становятся свойствами конструктора;
  • статические методы превращаются в функции, присвоенные классу;
  • порядок инициализации сохраняется линейно сверху вниз;
  • приватные статические члены эмулируются через WeakMap/WeakSet;
  • поведение this внутри статических методов переписывается на явную ссылку класса;
  • режимы трансформации влияют на точность и сложность генерируемого кода.