12 июня 2024 г.

Операторы нулевого слияния и присваивания: '??', '??='

Новая возможность
Эта возможность была добавлена в язык недавно. В старых браузерах может понадобиться полифил.

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

Оператор нулевого слияния представляет собой два вопросительных знака ??.

Так как он обрабатывает null и undefined одинаковым образом, то для этой статьи мы введём специальный термин. Для краткости будем говорить, что значение «определено», если оно не равняется ни null, ни undefined.

Результат выражения a ?? b будет следующим:

  • если a определено, то a,
  • если a не определено, то b.

Иначе говоря, оператор ?? возвращает первый аргумент, если он не null/undefined, иначе второй.

Оператор нулевого слияния не является чем-то принципиально новым. Это всего лишь удобный синтаксис, как из двух значений получить одно, которое «определено».

Вот как можно переписать выражение result = a ?? b, используя уже знакомые нам операторы:

result = (a !== null && a !== undefined) ? a : b;

Теперь должно быть абсолютно ясно, что делает ??. Давайте посмотрим, где это может быть полезно.

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

Например, здесь мы отобразим user, если её значение не null/undefined, в противном случае Аноним:

let user;

alert(user ?? "Аноним"); // Аноним (user не существует)

А вот пример, когда user присвоено значение:

let user = "Иван";

alert(user ?? "Аноним"); // Иван (user существует)

Кроме этого, можно записать последовательность из операторов ??, чтобы получить первое значение из списка, которое не является null/undefined.

Допустим, у нас есть данные пользователя в переменных firstName, lastName или nickName. Все они могут не существовать, если пользователь решил не вводить соответствующие значение.

Мы хотели бы отобразить имя пользователя, используя одну из этих переменных, или показать «Аноним», если все они null/undefined.

Для этого воспользуемся оператором ??:

let firstName = null;
let lastName = null;
let nickName = "Суперкодер";

// показывает первое значение, которое определено:
alert(firstName ?? lastName ?? nickName ?? "Аноним"); // Суперкодер

Сравнение с ||

Оператор ИЛИ || можно использовать для того же, что и ??, как это было показано в предыдущей главе.

Например, если в приведённом выше коде заменить ?? на ||, то будет тот же самый результат:

let firstName = null;
let lastName = null;
let nickName = "Суперкодер";

// показывает первое истинное значение:
alert(firstName || lastName || nickName || "Аноним"); // Суперкодер

Исторически сложилось так, что оператор ИЛИ || появился первым. Он существует с самого начала в JavaScript, поэтому разработчики долгое время использовали его для таких целей.

С другой стороны, сравнительно недавно в язык был добавлен оператор нулевого слияния ?? – как раз потому, что многие были недовольны оператором ||.

Важное различие между ними заключается в том, что:

  • || возвращает первое истинное значение.
  • ?? возвращает первое определённое значение.

Проще говоря, оператор || не различает false, 0, пустую строку "" и null/undefined. Для него они все одинаковы, т.е. являются ложными значениями. Если первым аргументом для оператора || будет любое из перечисленных значений, то в качестве результата мы получим второй аргумент.

Однако на практике часто требуется использовать значение по умолчанию только тогда, когда переменная является null/undefined. Ведь именно тогда значение действительно неизвестно/не определено.

Рассмотрим следующий пример:

let height = 0;

alert(height || 100); // 100
alert(height ?? 100); // 0
  • height || 100 проверяет height на ложное значение, оно равно 0, да, ложное.
    • поэтому результатом || является второй аргумент, т.е. 100.
  • height ?? 100 проверяет, что переменная height содержит null/undefined, а поскольку это не так,
    • то результатом является сама переменная height, т.е. 0.

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

Приоритет

Приоритет оператора ?? такой же, как и у ||. Они оба равны 3 в таблице на MDN.

Это означает, что, как и ||, оператор нулевого слияния ?? вычисляется до = и ?, но после большинства других операций, таких как +, *.

Так что, в выражениях такого вида понадобятся скобки:

let height = null;
let width = null;

// важно: используйте круглые скобки
let area = (height ?? 100) * (width ?? 50);

alert(area); // 5000

Иначе, если опустить скобки, оператор * выполнится первым, так как у него приоритет выше, чем у ??, и это приведёт к неправильным результатам.

// без скобок
let area = height ?? 100 * width ?? 50;

// ...сработает вот так (совсем не как нам нужно):
let area = height ?? (100 * width) ?? 50;

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

По соображениям безопасности JavaScript запрещает использование оператора ?? вместе с && и ||, если приоритет явно не указан при помощи круглых скобок.

Выполнение следующего кода приведёт к синтаксической ошибке:

let x = 1 && 2 ?? 3; // Синтаксическая ошибка

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

Используйте скобки, чтобы обойти это ограничение:

let x = (1 && 2) ?? 3; // Работает без ошибок

alert(x); // 2

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

Предположим, нам необходимо проверить, равна ли переменная null или undefined, и если это так — присвоить этой переменной какое-либо другое значение.

Вот как мы сделали бы это сейчас:

let userAge = null;

if (userAge === null || userAge === undefined) {
  userAge = 18;
}

Выглядит громоздко, правда? Существует оператор, более подходящий для подобных задач. Вот его синтаксис:

x ??= y

Оператор ??= присвоит x значение y только в том случае, если x не определено (null/undefined).

Теперь попробуем переписать уже знакомый нам фрагмент кода используя новый оператор:

let userAge = null;

userAge ??= 18;

alert(userAge) // 18

Обратите внимание: если бы userAge не был равен null/undefined, то выражение справа от ??= никогда бы не выполнилось:

let userAge = 18;

userAge ??= alert("не сработает");
userAge ??= 21;
userAge ??= null;

alert(userAge) // по-прежнему 18

Итого

  • Оператор нулевого слияния ?? — это быстрый способ выбрать первое «определённое» значение из списка.

    Используется для присвоения переменным значений по умолчанию:

    // будет height=100, если переменная height равна null или undefined
    height = height ?? 100;
  • Оператор ?? имеет очень низкий приоритет, лишь немного выше, чем у ? и =, поэтому при использовании его в выражении, скорее всего, потребуются скобки.

  • Запрещено использовать вместе с || или && без явно указанного приоритета, то есть без скобок.

  • Для присвоения переменной значения в зависимости от того, «определена» она или нет, используется оператор нулевого присваивания ??=.

Задачи

важность: 3

Что выведет код ниже?

alert(undefined ?? NaN ?? null ?? "" ?? " ");

Ответ: NaN. Это первое «определённое» значение.

alert(undefined ?? NaN ?? null ?? "" ?? " ");
важность: 3

Что будет выведено в итоге?

let city = null;

city ??= "Берлин";
city ??= null;
city ??= "Кёльн";
city ??= "Гамбург";

alert(city);

Ответ: "Берлин".

Первое присваивание city ??= "Берлин" срабатывает, поскольку изначально city — это null. После присваивания все остальные действия с оператором ??= становятся бессмысленными, так как теперь city содержит «определённое» значение.

let city = null;

city ??= "Берлин";
city ??= null;
city ??= "Кёльн";
city ??= "Гамбург";

alert(city);
важность: 3

Перепишите этот код используя операторы нулевого слияния и присваивания.

let num1 = 10,
    num2 = 20,
    result;

if (result === null || result === undefined) {
  if (num1 !== null && num1 !== undefined) {
    result = num1;
  } else {
    result = num2;
  }
}
let num1 = 10,
    num2 = 20,
    result;

result ??= num1 ?? num2;
Карта учебника