Деструктуризация в современном JavaScript является синтаксическим сахаром над доступом к свойствам объектов и элементам массивов. В исходном коде она выглядит декларативно, но при компиляции в более старые версии ECMAScript преобразуется в последовательность операций присваивания.
SWC выполняет трансформацию деструктуризации через модуль преобразования
AST, где каждый шаблон деструктуризации разворачивается в эквивалентный
императивный код. Основная задача трансформера — сохранить семантику
спецификации ECMAScript, включая порядок вычисления, побочные эффекты и
поведение при undefined или null.
Исходный код:
const { a, b } = obj;
После трансформации:
const _obj = obj;
const a = _obj.a;
const b = _obj.b;
Ключевой аспект заключается в создании промежуточной ссылки на объект,
чтобы исключить повторные вычисления выражения obj, если
оно имеет побочные эффекты:
const { a, b } = getObject();
Трансформируется в:
const _obj = getObject();
const a = _obj.a;
const b = _obj.b;
Это поведение соответствует строгой семантике оценки выражений слева направо.
Деструктуризация с алиасами:
const { a: x, b: y } = obj;
Преобразуется в:
const _obj = obj;
const x = _obj.a;
const y = _obj.b;
SWC сохраняет связь между исходным ключом и целевой переменной через промежуточные ссылки, избегая конфликтов идентификаторов на уровне scope-hoisting.
Вложенные структуры разворачиваются рекурсивно:
const { a: { b } } = obj;
Трансформация:
const _obj = obj;
const _a = _obj.a;
const b = _a.b;
Каждый уровень вложенности материализуется в отдельную временную
переменную, что упрощает контроль над null/undefined
поведением.
const [a, b] = arr;
После трансформации:
const _arr = arr;
const a = _arr[0];
const b = _arr[1];
При пропусках элементов:
const [a, , c] = arr;
Результат:
const _arr = arr;
const a = _arr[0];
const c = _arr[2];
SWC не создает лишних переменных для пропущенных элементов, сохраняя минимальность результата.
const { a = 10 } = obj;
Трансформация учитывает оператор nullish-логики:
const _obj = obj;
const a = _obj.a === undefined ? 10 : _obj.a;
В зависимости от конфигурации может использоваться более современная
форма с ??:
const a = _obj.a ?? 10;
Выбор зависит от целевого окружения и настроек пресета.
Параметры функций с деструктуризацией являются одной из наиболее сложных трансформаций, поскольку требуют сохранения поведения при вызове функции без аргументов.
function fn({ a, b }) {
return a + b;
}
Базовая трансформация:
function fn(_ref) {
const { a, b } = _ref;
return a + b;
}
Однако такое преобразование некорректно при вызове fn() без
аргументов. Поэтому SWC добавляет защитный слой:
function fn(_ref) {
const _ref1 = _ref === undefined ? {} : _ref;
const { a, b } = _ref1;
return a + b;
}
function fn({ a = 1, b = 2 } = {}) {
return a + b;
}
После трансформации появляется каскад проверок:
function fn(_ref) {
const _ref1 = _ref === undefined ? {} : _ref;
const a = _ref1.a === undefined ? 1 : _ref1.a;
const b = _ref1.b === undefined ? 2 : _ref1.b;
return a + b;
}
Каждое значение по умолчанию вычисляется независимо, что соответствует спецификации ECMAScript, где каждый дефолт является отдельным выражением.
function fn({ a = getA() } = {}) {}
SWC гарантирует, что getA() вызывается только при
необходимости:
function fn(_ref) {
const _ref1 = _ref === undefined ? {} : _ref;
const a = _ref1.a === undefined ? getA() : _ref1.a;
}
Порядок вычисления сохраняется строго: сначала проверка существования значения, затем выполнение функции.
В режиме loose трансформация становится менее точной, но
более производительной:
const { a } = obj;
может быть преобразовано в:
const a = obj.a;
без создания промежуточной переменной. Это ускоряет выполнение, но меняет поведение в редких кейсах, связанных с геттер-вычислениями.
В строгом режиме:
const { a } = obj;
если a — getter, он может быть вызван через промежуточный
объект:
const _obj = obj;
const a = _obj.a;
В loose режиме доступ может происходить напрямую, что
влияет на семантику побочных эффектов.
SWC активно использует генерацию уникальных идентификаторов:
_obj
_ref
_ref1
_temp
Эти переменные создаются в рамках текущего scope и гарантированно не конфликтуют с пользовательскими идентификаторами.
Алгоритм основан на анализе scope tree и проверке занятости имен, что предотвращает коллизии при вложенных трансформациях:
const { a: { b: { c } } } = obj;
→
const _obj = obj;
const _a = _obj.a;
const _b = _a.b;
const c = _b.c;
Деструктуризация часто комбинируется с:
Например:
const fn = ({ a = 1 }) => a;
→
const fn = (_ref) => {
const _ref1 = _ref === undefined ? {} : _ref;
const a = _ref1.a === undefined ? 1 : _ref1.a;
return a;
};
Такие цепочки трансформаций требуют строгого соблюдения порядка применения плагинов внутри пайплайна SWC, чтобы избежать неконсистентных AST-состояний.
На уровне AST деструктуризация представляется как
ObjectPattern и ArrayPattern. SWC проходит
несколько стадий:
undefined
Особое внимание уделяется сохранению порядка вычислений, поскольку спецификация ECMAScript строго определяет последовательность инициализации переменных в деструктуризации.
При ошибочных сценариях:
const { a } = null;
трансформированный код не подавляет ошибку, а сохраняет поведение
рантайма, где доступ к свойствам null приводит к исключению
TypeError.