Логические операторы: &&, || и ??

ветвления≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Операторы &&, || и ?? в JavaScript устроены хитрее, чем кажется: они не отвечают «да» или «нет», а возвращают одно из значений, которые в них передали, — и часто даже не смотрят на правую половину выражения. Разберёмся, как это работает и почему привычное x || 100 однажды съест ваш ноль.

Разделы темы:·

Кажется, что || и && отвечают true или false. На самом деле они возвращают одно из переданных значений. a || b — «первое истинное, а если истинных нет, то последнее»; a && b — «первое ложное, а если ложных нет, то последнее».

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Операнды вычисляются слева направо, и вычисление обрывается, как только ответ ясен. Это и есть короткое замыкание: если левая часть || уже истинна, правая не выполняется вообще — со всеми вызовами функций, которые в ней были.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Из правила «первое истинное» вырос популярный приём: значение || запасной вариант. Если слева пусто, подставится запасное.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Беда в том, что || спрашивает не «есть ли значение», а «истинно ли оно». Ложными считаются 0, пустая строка, null, undefined, NaN и false. Все они будут заменены запасным вариантом — даже если пользователь выбрал их осознанно.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Подсказка на пальцах: || спрашивает «есть ли тут что-то полезное?», а ?? — «есть ли тут хоть что-нибудь?»

?? — оператор нулевого слияния. Он подставляет правый операнд, только если слева null или undefined. Ноль, пустая строка и false для него — обычные данные, которые трогать не надо.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Правило выбора простое. Если 0 или пустая строка — допустимые значения (ширина, количество, комментарий), берите ??. Если любое «пустое» значение нужно заменить — например, пустое имя на «Гость», — || здесь уместнее.

Запись a ?? b || c двусмысленна: непонятно, что считать раньше. Авторы языка решили не угадывать за программиста и запретили такое сочетание на уровне синтаксиса — движок бросит SyntaxError ещё до запуска. Лечится скобками.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

! делает две вещи сразу: приводит значение к булеву типу и переворачивает его. Поэтому !0 — это true. Двойное !! переворачивает дважды, то есть просто отвечает на вопрос «истинно ли это?» — ручной аналог Boolean(x).

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
ЗначениеBoolean(значение)
0false
пустая строка ""false
nullfalse
undefinedfalse
NaNfalse
строка "0"true
[] — пустой массивtrue
{} — пустой объектtrue

Ложных значений в языке всего восемь, и полный их список с разбором — в статье «Условия: if, else и тернарный оператор». Для || и ?? из этого следует одно: || подменит запасным вариантом любое из восьми, а ?? — только null и undefined.

Когда операторы стоят рядом, порядок такой: сначала !, затем сравнения (>, === и другие), затем &&, и в самом конце || и ??. Значит, a || b && c читается как a || (b && c), а x > 0 && y > 0 работает без скобок именно так, как ожидается.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

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

Есть логические операторы присваивания. x ??= y присвоит y, только если x равен null или undefined; x ||= y — если x ложный. Они тоже ленивые: правая часть не вычисляется, когда присваивать не нужно.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

  • || и && возвращают один из операндов, а не true/false, и не вычисляют правую часть, если ответ уже понятен по левой.
  • Приём «значение || запасное» подводит на 0, пустой строке и false: они ложные, хотя и осмысленные.
  • ?? подставляет запасное значение только вместо null и undefined; смешивать его с || и && без скобок запрещено синтаксисом.
  • !x приводит к булеву типу с переворотом, !!x — просто приведение, то же самое, что Boolean(x).
  • Порядок вычисления: !, затем сравнения, затем &&, затем || и ??. Сомневаетесь — ставьте скобки.

1/3Что выведет console.log(0 || "" || "хвост");?

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

3/3Что выведет console.log(false || 0 && 1);?

Верните ноль на место

Функция подставляет значения по умолчанию, но теряет осознанный ноль и пустую строку. Почините её так, чтобы запасное значение появлялось только когда параметра действительно нет.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Достаточно поменять || на ??: ноль и пустая строка — законные значения, а не «пустота».

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Если бы по замыслу пустое имя нужно было всё-таки заменять на «Блок», то для label правильнее оставить || — выбор оператора зависит от того, считаете вы пустую строку данными или отсутствием данных.

Предскажите вывод

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

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

В первой строке истинных операндов нет, поэтому || возвращает последний — 0.

Во второй первый ?? отдаёт 0, а ноль — не пустота, поэтому второй ?? его и оставляет.

В третьей строка "0" непустая, значит истинная: содержимое роли не играет.

В четвёртой && ищет первое ложное значение, находит 0 и до 3 уже не доходит.