Деструктуризация и параметры по умолчанию

Деструктуризация в современном 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;
}

Порядок вычисления сохраняется строго: сначала проверка существования значения, затем выполнение функции.


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

Лоose mode

В режиме 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;

Связь с другими трансформациями

Деструктуризация часто комбинируется с:

  • параметрами по умолчанию функций
  • стрелочными функциями
  • async/await трансформациями
  • классами и полями экземпляра

Например:

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-пайплайне

На уровне AST деструктуризация представляется как ObjectPattern и ArrayPattern. SWC проходит несколько стадий:

  • нормализация паттернов
  • выделение побочных выражений
  • генерация временных идентификаторов
  • вставка проверок undefined
  • разворачивание вложенных структур

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

При ошибочных сценариях:

const { a } = null;

трансформированный код не подавляет ошибку, а сохраняет поведение рантайма, где доступ к свойствам null приводит к исключению TypeError.