Трансформация классов и наследования

SWC (Speedy Web Compiler) реализует собственный высокопроизводительный трансформатор JavaScript/TypeScript кода, в котором поддержка классов и наследования занимает ключевое место. При обработке современных синтаксических конструкций SWC преобразует их в совместимый ES5/ES2015 код, сохраняя семантику спецификации ECMAScript и оптимизируя результирующий вывод для выполнения в широком спектре окружений.

Конструкция class в современном JavaScript является синтаксическим сахаром над прототипным наследованием. SWC выполняет декомпозицию классов в набор функций, присваиваний свойств прототипу и вспомогательных операций, обеспечивающих корректную инициализацию экземпляров.

Базовый класс:

class A {
  constructor(x) {
    this.x = x;
  }

  method() {
    return this.x;
  }
}

После трансформации SWC (упрощённая форма):

function A(x) {
  this.x = x;
}

A.prototype.method = function () {
  return this.x;
};

Ключевая цель трансформации — сохранить поведение this, прототипные цепочки и контекст вызова методов.

Конструктор и инициализация экземпляра

Метод constructor всегда преобразуется в обычную функцию-конструктор. Все поля, объявленные внутри конструктора, остаются частью инициализации экземпляра.

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

  • вызовы super()
  • инициализацию полей класса
  • условные ветвления
  • побочные эффекты

Пример:

class A {
  constructor() {
    this.a = 1;
    this.b = 2;
  }
}

Результат:

function A() {
  this.a = 1;
  this.b = 2;
}

Прототипные методы

Методы класса переносятся в prototype. SWC не использует Object.defineProperty по умолчанию, если не активированы дополнительные опции, такие как сохранение дескрипторов.

class A {
  method() {
    return 42;
  }
}

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

A.prototype.method = function () {
  return 42;
};

При этом сохраняется важное свойство: методы не перечисляемы в циклах for…in, если используется корректная конфигурация трансформации с дескрипторами.

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

Статические члены класса переносятся непосредственно на функцию-конструктор.

class A {
  static foo() {
    return 10;
  }
}

Результат:

A.foo = function () {
  return 10;
};

Статические свойства также могут трансформироваться в присваивания:

class A {
  static version = 1;
}

При включённой поддержке полей класса:

A.version = 1;

Наследование классов

Наиболее сложный аспект трансформации — поддержка наследования через extends. SWC должен корректно построить цепочку прототипов и обеспечить доступ к super.

Базовый механизм наследования

class A {
  constructor(x) {
    this.x = x;
  }
}

class B extends A {
  constructor(x, y) {
    super(x);
    this.y = y;
  }
}

Трансформация включает несколько ключевых шагов:

  1. Создание функции-конструктора B
  2. Вызов родительского конструктора через A.call
  3. Установка прототипной цепочки
  4. Добавление методов

Упрощённый результат:

function B(x, y) {
  A.call(this, x);
  this.y = y;
}

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

Вызовы super в методах

Особое внимание SWC уделяет корректной обработке super.method() и super().

Вызов super в конструкторе

class A {
  constructor(x) {
    this.x = x;
  }
}

class B extends A {
  constructor(x) {
    super(x);
    this.y = 10;
  }
}

Трансформация требует сохранения правильного контекста:

function B(x) {
  A.call(this, x);
  this.y = 10;
}

SWC гарантирует, что super() вызывается до обращения к this, что соответствует спецификации.

Наследование методов и переопределение

При переопределении методов SWC просто заменяет функцию на прототипе дочернего класса.

class A {
  method() {
    return 1;
  }
}

class B extends A {
  method() {
    return 2;
  }
}

Результат:

function A() {}

A.prototype.method = function () {
  return 1;
};

function B() {
  A.apply(this, arguments);
}

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

B.prototype.method = function () {
  return 2;
};

Использование super в методах

Когда метод дочернего класса вызывает super.method(), SWC создаёт внутреннюю ссылку на прототип родителя.

class A {
  method() {
    return 1;
  }
}

class B extends A {
  method() {
    return super.method() + 1;
  }
}

Трансформация требует создания вспомогательных функций (упрощённо):

B.prototype.method = function () {
  return A.prototype.method.call(this) + 1;
};

Фактически SWC использует более сложную обвязку для корректного поиска метода в цепочке прототипов, особенно при динамическом наследовании.

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

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

class A {
  static foo() {
    return 1;
  }
}

class B extends A {}

После трансформации:

function A() {}

A.foo = function () {
  return 1;
};

function B() {}

B.__proto__ = A;
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

Таким образом, B.foo доступен через цепочку [[Prototype]] функции.

Поля классов и наследование

Поля экземпляра могут объявляться вне конструктора:

class A {
  x = 1;
}

class B extends A {
  y = 2;
}

SWC преобразует это в инициализацию внутри конструктора:

function A() {
  this.x = 1;
}

function B() {
  A.call(this);
  this.y = 2;
}

Порядок инициализации строго сохраняется:

  1. Родительский конструктор
  2. Поля родителя
  3. Конструктор потомка
  4. Поля потомка

Private поля и методы

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

class A {
  

  getX() {
    return this.#x;
  }
}

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

var _x = new WeakMap();

function A() {
  _x.set(this, 10);
}

A.prototype.getX = function () {
  return _x.get(this);
};

Этот подход обеспечивает инкапсуляцию и предотвращает доступ извне класса.

Проверка instanceof и корректность прототипов

SWC сохраняет поведение оператора instanceof, поскольку прототипная цепочка строится через Object.create.

B.prototype = Object.create(A.prototype);

Это гарантирует:

  • new B() instanceof B === true
  • new B() instanceof A === true

Особенности оптимизации трансформации

SWC применяет ряд оптимизаций:

  • минимизация вспомогательных вызовов
  • повторное использование ссылок на прототип
  • устранение избыточных Object.defineProperty при упрощённой конфигурации
  • инлайнинг простых методов при включённых оптимизациях

Также SWC может различать режимы трансформации:

  • legacy (ES5 совместимость)
  • loose mode (упрощённые преобразования)
  • spec mode (строгое соответствие ECMAScript)

Различия loose и spec режимов

В loose режиме:

  • методы напрямую присваиваются на прототип
  • отсутствуют дескрипторы свойств
  • упрощается работа с super
class A {
  method() {}
}

Loose:

A.prototype.method = function () {};

Spec:

Object.defineProperty(A.prototype, "method", {
  value: function () {},
  enumerable: false,
  writable: true,
  configurable: true,
});

Выбор режима влияет на производительность и точность соответствия стандарту.

Взаимодействие классов и замыканий

SWC корректно обрабатывает классы, вложенные в функции, включая доступ к внешним переменным:

function createClass(x) {
  return class {
    getX() {
      return x;
    }
  };
}

Трансформация сохраняет замыкание:

function createClass(x) {
  return function () {
    this.getX = function () {
      return x;
    };
  };
}

(в реальной реализации структура может быть более оптимизирована через прототип).

Итоговая модель трансформации

Внутренняя модель SWC для классов и наследования сводится к трём базовым механизмам:

  • функции-конструкторы
  • прототипные цепочки
  • вспомогательные операции для super, private и static

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