Умное значение по умолчанию
В JavaScript издавна есть короткий способ задать дефолтное значение: value || fallback. В целом работает, но с подвохом — любое falsy-значение воспринимается как «пустое». 0, '' и false тоже запускают подстановку запасного варианта, даже если именно они и есть настоящий ответ.
Оператор nullish coalescing ?? решает эту проблему. Он подставляет запасное значение только тогда, когда слева null или undefined:
0 и '' остаются. А вот null и undefined — нет. Вот и весь оператор.
Почему || не спасает
Вот тот самый классический баг, ради которого и придумали ??:
Пользователь явно указал громкость 0 (тишина) и пустой ник. Оба значения тихо затёрлись, потому что || не умеет отличать «ничего не задано» от просто falsy-значения.
Меняем на ??:
Теперь значения 0 и '' проходят без изменений, а дефолт подставляется только для реально отсутствующих полей. В 99% случаев именно это вам и нужно.
Как это работает в голове
У оператора ?? ровно один вопрос: слева null или undefined? Всё остальное его не волнует.
| Значение слева | left || right вернёт | left ?? right вернёт |
|---|---|---|
null | right | right |
undefined | right | right |
0 | right | left (0) |
'' | right | left ('') |
false | right | left (false) |
NaN | right | left (NaN) |
| любой объект | left | left |
Берите ??, когда в ваших данных falsy-значения имеют смысл. Берите ||, когда вам правда нужно отсечь всё falsy — пустые строки, нули и так далее. Такие случаи бывают, но гораздо реже, чем принято думать.
Короткое замыкание
Как и || с &&, оператор ?? работает через короткое замыкание. Если слева не nullish — правая часть вообще не выполняется:
expensiveDefault() вызовется только один раз — для b. Это удобно, когда в качестве запасного значения у вас идёт вызов функции, сетевой запрос или любая другая операция, которую лишний раз запускать не хочется.
Связка с опциональной цепочкой
Операторы ?? и ?. появились вместе в ES2020 и изначально задумывались как команда. Опциональная цепочка (?.) проходит по пути, в котором какое-то звено может отсутствовать, и возвращает undefined, если что-то не нашлось. А ?? затем подставляет разумное значение по умолчанию:
Без этой парочки тот же код превращается в уродливую цепочку проверок через && или в try/catch. А с ними безопасный доступ к свойствам и разумные значения по умолчанию укладываются в одну строку.
Оператор ??=: присваивание при null или undefined
a ??= b присваивает b переменной a только тогда, когда a равно null или undefined. Это и есть оператор логического nullish-присваивания, и он работает с коротким замыканием — если у a уже есть значение, правая часть попросту не вычисляется.
Обрати внимание: verbose: false и retries: 0 остались нетронутыми — ??= подставляет значение только туда, где его реально нет. Для сравнения: ||= затёр бы оба этих поля.
Приоритет операторов и правило скобок
Оператор ?? намеренно отказывается сочетаться с || или && без скобок. Такой код выбросит SyntaxError:
const x = a || b ?? c; // SyntaxError
Разработчики языка посчитали, что приоритет операторов здесь слишком неоднозначный, и решили заставить нас явно указывать намерения. Достаточно добавить скобки — и всё заработает:
Разные группировки — разные ответы. Скобки здесь не для красоты: они говорят и парсеру, и тому, кто будет читать код после вас, что вы на самом деле имели в виду.
Это не то же самое, что значение по умолчанию в параметре функции
У параметров по умолчанию в сигнатуре функции своё правило: они срабатывают только тогда, когда аргумент равен undefined, но не null. И это тонкое отличие от ??, который одинаково реагирует на оба случая.
Если нужно, чтобы null тоже запускал значение по умолчанию, используйте ?? прямо в теле функции:
Небольшая, но легко обжигающая разница — особенно когда API возвращает null в значении «ничего не задано».
Когда использовать ??
По умолчанию берите именно ??, если нет веской причины поступить иначе. Этот оператор лучше всего ложится на то, как данные ведут себя в реальности: 0, '' и false — это, как правило, осмысленные значения, которые стоит сохранить, а подменять на дефолт нужно только по-настоящему отсутствующие данные. || оставьте для случаев, когда вам действительно нужно заменить любое falsy-значение.
Дальше: классы
На этом с объектами и массивами закончили — у вас уже есть всё, чтобы безопасно собирать структуры данных и ходить по ним. Следующая глава — про организацию поведения: классы, наследование и прототипная система, которая лежит в их основе.
Часто задаваемые вопросы
Что такое оператор нулевого слияния в JavaScript?
?? возвращает правый операнд только если левый равен null или undefined. Во всех остальных случаях возвращается левый операнд как есть. Запись value ?? fallback — это стандартный способ задать значение по умолчанию, не спотыкаясь на таких валидных, но falsy-значениях, как 0 или пустая строка ''.
В чём разница между ?? и || в JavaScript?
|| срабатывает на любом falsy-значении: 0, '', false, NaN, null, undefined. А ?? — только на null и undefined. Если 0 или пустая строка для ваших данных — это нормальное, осмысленное значение, нужен именно ??. А вот когда действительно хочется отсечь все falsy-значения разом, || по-прежнему к месту.
Можно ли сочетать ?? с опциональной цепочкой ?.?
Да, это классическая связка. Выражение user?.settings?.theme ?? 'light' безопасно проходит по цепочке, возвращает undefined, если где-то по пути значения нет, и тут же подставляет 'light'. Оба оператора не случайно появились вместе в ES2020 — именно под этот сценарий.
Что делает оператор ??=?
a ??= b присваивает b переменной a, только если в a сейчас лежит null или undefined. По сути это короткая запись для a = a ?? b, но с короткозамкнутой логикой: если в a уже что-то есть, правая часть даже не вычислится. Удобно для того, чтобы дозаполнять недостающие поля в объекте настроек.