W3docs

Оператор нулевого слияния в JavaScript (??)

Оператор нулевого слияния (??): отличие от ||, приоритет операторов и логические операторы присваивания ??=, &&= и ||=.

Оператор нулевого слияния (??) — это краткий и выразительный способ задать значение по умолчанию в JavaScript. Он возвращает правый операнд только тогда, когда левый операнд является нулевым — то есть null или undefined — а во всех остальных случаях возвращает левое значение без изменений. Появившись в ES2020, он решает давнюю проблему использования оператора || для задания значений по умолчанию и отлично сочетается с опциональной цепочкой и уже знакомыми вам логическими операторами.

Что означает «нулевое» значение

Значение считается нулевым, если оно в точности равно null или undefined. Любое другое значение — включая ложные (0, '', false, NaN) — воспринимается оператором ?? как настоящее, намеренное значение.

null ?? 'default'       // 'default'
undefined ?? 'default'  // 'default'

0 ?? 'default'          // 0
'' ?? 'default'         // ''
false ?? 'default'      // false
NaN ?? 'default'        // NaN

Правило простое: если левая часть равна null или undefined, возвращается правая часть; в противном случае — левая.

javascript— editable

Чем ?? отличается от ||

Именно ради этого и появился оператор ??. Логический оператор ИЛИ (||) возвращает правую часть всякий раз, когда левая часть является любым ложным значением — 0, '', false, NaN, null и undefined — все они подпадают под это условие. Такое поведение приемлемо до тех пор, пока 0 или пустая строка не являются допустимыми значениями; в противном случае || тихо выдаёт неверный результат.

Рассмотрим счётчик, допустимым значением которого может быть 0:

let count = 0;

let withOr = count || 100;   // 100  — wrong! 0 got replaced
let withNullish = count ?? 100; // 0   — correct, 0 was preserved

Поскольку 0 является ложным значением, || обрабатывает его так же, как «нет значения», и переходит к 100. Оператор ?? распознаёт, что 0 не является нулевым, и сохраняет его. Та же ловушка применима к пустым строкам:

javascript— editable
Внимание

Используйте ?? вместо ||, когда 0, '' или false являются допустимыми входными данными. Применение || для задания значений по умолчанию в таких случаях — один из наиболее распространённых источников трудноуловимых ошибок в JavaScript: например, количество 0 может превратиться в 1, или пустое поле поиска получит какой-нибудь текст-заполнитель.

Приоритет и скобки

Оператор ?? имеет низкий приоритет — примерно такой же, как у ||, и ниже, чем у арифметических и операторов сравнения. На практике это означает, что запасное значение вычисляется прежде, чем результат используется в большем выражении, однако для ясности намерений всё же рекомендуется использовать скобки.

let height = null;
let width;

// Each ?? is evaluated, then the products are multiplied
let area = (height ?? 100) * (width ?? 50);
console.log(area); // 100 * 50 = 5000

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

Совместное использование ?? с && или || запрещено

JavaScript намеренно не позволяет смешивать ?? с && или || в одном выражении без скобок. Это вызывает SyntaxError, поскольку приоритет между ними был бы неоднозначным и легко допускал бы ошибки при чтении.

// SyntaxError: Unexpected token '||'
let result = a ?? b || c;

// Fix it by grouping explicitly:
let safe = (a ?? b) || c;   // valid
let other = a ?? (b || c);  // also valid, different meaning

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

Примечание

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

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

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

Присваивание при нулевом значении (??=)

x ??= y присваивает y переменной x только если x в данный момент равна null или undefined. Это форма присваивания для ?? и наиболее распространённый из трёх операторов для установки значений по умолчанию в объекте.

let settings = { theme: 'dark' };

settings.theme ??= 'light';   // already set → stays 'dark'
settings.fontSize ??= 16;     // missing → becomes 16

console.log(settings); // { theme: 'dark', fontSize: 16 }

Логическое присваивание ИЛИ (||=)

x ||= y присваивает y переменной x, когда x является ложным значением (0, '', false, null, undefined, NaN). Как и ||, он может перезаписывать допустимые ложные значения, поэтому используйте его только тогда, когда любое ложное значение действительно означает «замени меня».

javascript— editable

Логическое присваивание И (&&=)

x &&= y присваивает y переменной x только когда x является истинным значением. Это удобно для обновления уже существующего значения без его создания.

let config = { token: 'abc123' };

config.token &&= config.token.trim(); // truthy → trimmed and reassigned
config.missing &&= 'never set';        // undefined → left untouched

console.log(config); // { token: 'abc123' }

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

Примеры из реальной практики

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

// 1. Default options merged with user input
function createButton(options) {
  let label = options.label ?? 'Submit';
  let count = options.count ?? 0; // 0 stays 0, never becomes a default
  return { label, count };
}

// 2. Reading a possibly-missing API field
let response = { data: { user: { nickname: null } } };
let name = response.data.user.nickname ?? 'Guest';
console.log(name); // 'Guest'

Оператор отлично сочетается с опциональной цепочкой (?.). Опциональная цепочка безопасно возвращает undefined, когда часть пути отсутствует, а ?? затем задаёт запасное значение в одну лаконичную строку:

let city = user?.address?.city ?? 'Unknown';

Это читается так: «Используй город пользователя, если вся цепочка существует, иначе используй 'Unknown'.» Для выбора одного из двух путей на основе условия лучше подходит условный (тернарный) оператор — ?? предназначен специально для задания значений по умолчанию при нулевых значениях.

Информация

Поддержка браузерами: ?? входит в состав ES2020, а логические операторы присваивания (??=, ||=, &&=) появились в ES2021. Все современные браузеры и Node.js 14+ поддерживают их. Если необходима поддержка очень старых окружений, транспилятор наподобие Babel может преобразовать их.

Итоги

  • ?? возвращает правый операнд только когда левый равен null или undefined; в остальных случаях возвращает левый.
  • Предпочитайте ?? вместо || для задания значений по умолчанию, когда 0, '' или false являются допустимыми значениями.
  • ?? имеет низкий приоритет — группируйте с помощью скобок и никогда не ставьте его непосредственно рядом с && или || без скобок (это вызовет SyntaxError).
  • ??=, ||= и &&= выполняют присваивание условно: при нулевом, при ложном и при истинном значении соответственно.
  • Комбинируйте ?? с опциональной цепочкой (?.) для чтения потенциально отсутствующих данных и задания значения по умолчанию в одном выражении.

Проверьте свои знания

Практика
Чему равно выражение 0 ?? 100?
Чему равно выражение 0 ?? 100?
Практика
Почему запись a ?? b || c без скобок является проблемой?
Почему запись a ?? b || c без скобок является проблемой?
Практика
Что делает user.role ??= 'member', если user.role равно undefined?
Что делает user.role ??= 'member', если user.role равно undefined?
Was this page helpful?