Логические операторы присваивания

Логические операторы присваивания (||=, &&=, ??=) объединяют поведение логических операций и операции присваивания, позволяя изменять значение переменной только при выполнении определённого условия. В экосистеме JavaScript они относятся к современным возможностям языка (ES2021), а при использовании SWC часто становятся объектом трансформации для обеспечения совместимости с более старыми окружениями.


Каждый из операторов опирается на принцип короткого замыкания (short-circuit evaluation), что напрямую влияет на поведение присваивания.

||=

Оператор логического ИЛИ с присваиванием:

a ||= b;

Эквивалентен:

a || (a = b);

Поведение:

  • если a является truthy, присваивание не выполняется
  • если a является falsy, a получает значение b

Примеры:

let a = 0;
a ||= 10; // 10

let b = 5;
b ||= 10; // 5

&&=

Оператор логического И с присваиванием:

a &&= b;

Эквивалентен:

a && (a = b);

Поведение:

  • если a falsy — присваивание не происходит
  • если a truthy — a получает значение b

Примеры:

let a = 1;
a &&= 10; // 10

let b = 0;
b &&= 10; // 0

??=

Оператор нулевого слияния с присваиванием:

a ??= b;

Эквивалентен:

a ?? (a = b);

Поведение:

  • присваивание выполняется только если a равен null или undefined
  • в отличие от ||=, учитываются только nullish-значения

Примеры:

let a = 0;
a ??= 10; // 0

let b = null;
b ??= 10; // 10

Особенности реализации в SWC

SWC (@swc/core) является высокопроизводительным компилятором JavaScript/TypeScript, написанным на Rust. Он выполняет трансформации синтаксиса на этапе компиляции, включая преобразование логических операторов присваивания в эквивалентный ES5-совместимый код при необходимости.

Трансформация особенно важна для:

  • старых браузеров (ES2015 и ниже)
  • Node.js версий без поддержки ES2021
  • сборок с жёсткими требованиями к совместимости

Поведение трансформации SWC

При включённой трансформации SWC преобразует:

a ||= b;

в примерно:

a || (a = b);
a &&= b;

в:

a && (a = b);
a ??= b;

в:

a ?? (a = b);

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


Конфигурация SWC

Настройка трансформаций выполняется через .swcrc:

{
  "jsc": {
    "parser": {
      "syntax": "ecmascript"
    },
    "target": "es5"
  },
  "env": {
    "targets": "> 0.25%, not dead"
  }
}

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

Дополнительно поведение может зависеть от комбинации:

  • jsc.target
  • env.targets
  • используемого бандлера (Webpack, Vite, Rsbuild и др.)

Различие поведения ||= и ??=

Ключевая ошибка при миграции и трансформации заключается в путанице между truthy/falsy и nullish логикой.

Оператор Условие срабатывания
| | = falsy (0, ““, false, null, undefined, NaN)
??= только null и undefined

SWC строго сохраняет это различие при транспиляции, не унифицируя поведение.


Порядок вычисления и побочные эффекты

Логические операторы присваивания выполняют правую часть только при необходимости, что критично для выражений с побочными эффектами:

let a = 1;

a ||= console.log("executed");

Если a truthy, console.log не вызовется.

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


AST-представление в SWC

В SWC данные операторы представлены в AST как AssignmentExpression с оператором:

  • ||=
  • &&=
  • ??=

Упрощённая структура:

{
  "type": "AssignmentExpression",
  "operator": "||=",
  "left": { "type": "Identifier", "value": "a" },
  "right": { "type": "NumericLiteral", "value": 10 }
}

При трансформации AST переписывается в форму:

a || (a = 10)

что уже представляется как комбинация LogicalExpression и AssignmentExpression.


Взаимодействие с другими трансформациями SWC

Логические операторы присваивания могут взаимодействовать с:

  1. Optional chaining (?.)

Комбинации требуют аккуратной трансформации:

obj.prop ||= 1;
obj?.prop ||= 1;

Во втором случае SWC должен сохранить защиту от null.


  1. Minification

При минификации SWC (через minify) выражения могут быть дополнительно упрощены, но семантика не меняется. Например:

a || (a = b)

может быть сжато по пробелам, но не по структуре.


  1. TypeScript

При использовании @swc/core с TypeScript парсером логические операторы присваивания обрабатываются на уровне синтаксиса без дополнительной типовой логики. Ошибки типов не влияют на трансформацию.


Практические сценарии использования в проектах со SWC

Инициализация значений

config.timeout ||= 5000;

SWC преобразует это безопасно для старых рантаймов.


Установка значений по умолчанию

options.retries ??= 3;

Управление состоянием

state.loaded &&= checkIntegrity(state);

Потенциальные проблемы при трансформации

  1. Неправильное ожидание truthy-логики

Разница ||= и ??= часто становится источником логических ошибок после компиляции.

  1. Побочные эффекты в правой части

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

value ||= expensiveCall();

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

  1. Сложные цепочки выражений

a.b ||= c.d ||= getValue();

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


Поведение в различных версиях ECMAScript target

SWC может по-разному обрабатывать операторы в зависимости от target:

  • es2021 и выше — трансформация не выполняется
  • es2018 и ниже — выполняется полная транспиляция
  • es5 — всегда преобразуется в логические выражения с присваиванием

Производственные аспекты использования SWC

SWC ориентирован на высокую скорость трансформации, поэтому:

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

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