Стрелочные функции в 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-параметрами преобразуются с использованием стандартного массива аргументов.
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 при обработке стрелочных функций можно описать как последовательность этапов:
this, arguments,
super.
function.
_this,
_arguments) при необходимости.
return.
Такой подход позволяет добиться полной совместимости поведения стрелочных функций при трансляции в ES5, сохраняя при этом предсказуемую структуру и минимизируя избыточные обёртки там, где это возможно.