Трансформация стрелочных функций

Стрелочные функции в JavaScript обладают ключевым отличием от обычных функций — лексическим связыванием this, arguments, super и new.target. В процессе транспиляции SWC обязан сохранить поведение исходного кода, преобразуя стрелочные функции в эквивалентные конструкции более старого стандарта ECMAScript.

Основная задача трансформации заключается не только в синтаксической замене => на function, но и в корректной эмуляции области видимости, особенно в отношении контекста выполнения.


Базовое преобразование стрелочной функции

Простейшая форма стрелочной функции:

const add = (a, b) => a + b;

В процессе трансформации SWC преобразует её в обычную функцию-выражение:

var add = function (a, b) {
  return a + b;
};

Ключевой момент — неявный return в однострочном выражении становится явным возвратом внутри тела функции.

Если тело стрелочной функции заключено в фигурные скобки, поведение изменяется:

const add = (a, b) => {
  return a + b;
};

Результирующий код практически идентичен, за исключением замены синтаксиса:

var add = function (a, b) {
  return a + b;
};

Лексическое связывание this

Главное отличие стрелочных функций — отсутствие собственного this. SWC обязан сохранить лексическое поведение, заимствуя контекст внешней функции.

Рассмотрим пример:

function Timer() {
  this.seconds = 0;

  setInterval(() => {
    this.seconds++;
  }, 1000);
}

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

setInterval(function () {
  this.seconds++;
}, 1000);

В SWC применяется захват контекста через переменную:

function Timer() {
  var _this = this;

  this.seconds = 0;

  setInterval(function () {
    _this.seconds++;
  }, 1000);
}

Здесь _this сохраняет ссылку на внешний this, обеспечивая идентичную семантику.


Вложенные стрелочные функции и цепочки контекста

При нескольких уровнях вложенности SWC использует единый захваченный контекст, избегая повторного переопределения.

const obj = {
  value: 10,
  fn() {
    return () => () => {
      return this.value;
    };
  }
};

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

var obj = {
  value: 10,
  fn: function fn() {
    var _this = this;

    return function () {
      return function () {
        return _this.value;
      };
    };
  }
};

Контекст захватывается один раз на уровне ближайшей обычной функции.


Обработка аргументов arguments

Стрелочные функции не имеют собственного arguments. SWC подменяет его ссылкой на `arguments внешней функции.

function wrapper() {
  return () => arguments[0];
}

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

function wrapper() {
  var _arguments = arguments;

  return function () {
    return _arguments[0];
  };
}

Важно, что arguments сохраняется в переменную только при его использовании внутри стрелочной функции.


Стрелочные функции с параметрами по умолчанию

При наличии значений по умолчанию SWC сохраняет поведение через стандартную ES5-эмуляцию.

const sum = (a = 1, b = 2) => a + b;

Преобразуется в:

var sum = function (a, b) {
  if (a === void 0) a = 1;
  if (b === void 0) b = 2;
  return a + b;
};

Такой подход обеспечивает корректную работу даже в средах без поддержки default parameters.


Деструктуризация в параметрах стрелочных функций

Деструктуризация сохраняется, но оборачивается в обычные параметры функции:

const fn = ({ a, b }) => a + b;

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

var fn = function (_ref) {
  var a = _ref.a,
      b = _ref.b;
  return a + b;
};

SWC распаковывает объект вручную, создавая промежуточную переменную.


Rest-параметры

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

const fn = (...args) => args.length;

Результат:

var fn = function () {
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
    args[_key] = arguments[_key];
  }

  return args.length;
};

Такой подход обеспечивает совместимость с ES5-окружением без синтаксиса.


Стрелочные функции как методы объектов

Особое внимание уделяется случаям, когда стрелочная функция используется внутри объекта.

const obj = {
  value: 42,
  get: () => this.value
};

Такой код не имеет собственного this, и SWC не может привязать его к объекту. Трансформация сохраняет это поведение:

var obj = {
  value: 42,
  get: function () {
    return this.value;
  }.bind(undefined)
};

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

var obj = {
  value: 42,
  get: function () {
    return this.value;
  }
};

Именно семантика исходного окружения определяет итоговую форму.


Стрелочные функции в классах

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

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

  method = () => {
    return this.x;
  };
}

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

function A() {
  var _this = this;

  this.x = 1;

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

Если используется class fields transform, стрелочная функция становится свойством экземпляра, а контекст захватывается через _this.


Отличия поведения при оптимизации контекста

SWC стремится минимизировать количество вспомогательных переменных. Если стрелочная функция не использует this, arguments или super, дополнительный контекст не создаётся.

const fn = () => 1;

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

var fn = function () {
  return 1;
};

Без _this, без _arguments, без дополнительных накладных расходов.


Влияние вложенных областей видимости

При сложных структурах SWC анализирует необходимость захвата контекста на уровне AST.

function outer() {
  const a = 1;

  return () => {
    return () => a;
  };
}

Результат:

function outer() {
  var a = 1;

  return function () {
    return function () {
      return a;
    };
  };
}

Поскольку a — это переменная внешней области, дополнительных обёрток для this не требуется.


Потенциальные краевые случаи

Некоторые конструкции требуют особой осторожности:

Стрелочные функции и super

class A extends B {
  method() {
    return () => super.method();
  }
}

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

class A extends B {
  method() {
    var _this = this;

    return function () {
      return B.prototype.method.call(_this);
    };
  }
}

Стрелочные функции и new

Стрелочные функции не могут быть вызваны через new, поэтому SWC не добавляет никаких адаптаций, но гарантирует сохранение ошибки при попытке использования.


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

Внутренний алгоритм SWC при обработке стрелочных функций можно описать как последовательность этапов:

  1. Анализ использования this, arguments, super.
  2. Определение необходимости создания лексического контекста.
  3. Генерация функции-замены function.
  4. Добавление захвата внешних переменных (_this, _arguments) при необходимости.
  5. Преобразование параметров (default, rest, destructuring).
  6. Упрощение тела функции с сохранением семантики return.

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