|
| 1 | +# Оператор объединения с null '??' |
| 2 | + |
| 3 | +[recent browser="new"] |
| 4 | + |
| 5 | +В этой статье мы будем говорить, что значение выражение "определено", если оно отличается от `null` или `undefined`. |
| 6 | + |
| 7 | +Оператор объединения с null представляет собой два вопросительных знака `??`. |
| 8 | + |
| 9 | +Результат выражения `a ?? b` будет следующим: |
| 10 | +- `a`, если значение `a` определено, |
| 11 | +- `b`, если значение `a` не определено. |
| 12 | + |
| 13 | +То есть оператор `??` возвращает первый аргумент, если он не `null/undefined`, иначе второй. |
| 14 | + |
| 15 | +Оператор объединения с null не является чем-то принципиально новым. Это всего лишь удобный синтаксис, как из двух значений получить одно "определённое". |
| 16 | + |
| 17 | +Вот как можно переписать выражение `result = a ?? b`, используя уже знакомые нам операторы: |
| 18 | + |
| 19 | +```js |
| 20 | +result = (a !== null && a !== undefined) ? a : b; |
| 21 | +``` |
| 22 | + |
| 23 | +Как правило, оператор `??` нужен для того, чтобы задать значение по умолчанию для потенциально неопределённой переменной. |
| 24 | + |
| 25 | +Например, в следующем примере, если переменная `user` не определена, покажем модальное окно с надписью `Аноним`: |
| 26 | + |
| 27 | +```js run |
| 28 | +let user; |
| 29 | + |
| 30 | +alert(user ?? "Аноним"); // Аноним |
| 31 | +``` |
| 32 | +
|
| 33 | +Конечно, если бы переменная `user` содержала любое значение, кроме `null/undefined`, то мы бы увидели его: |
| 34 | +
|
| 35 | +```js run |
| 36 | +let user = "Иван"; |
| 37 | + |
| 38 | +alert(user ?? "Аноним"); // Иван |
| 39 | +``` |
| 40 | +
|
| 41 | +Кроме этого, можно записать последовательность из операторов `??`, чтобы получить первое значение из списка, которое не является `null/undefined`. |
| 42 | +
|
| 43 | +Допустим, у нас есть данные пользователя в переменных `firstName`, `lastName` или `nickName`. Все они могут быть неопределёнными, если отсутствует соответствующая информация. |
| 44 | +
|
| 45 | +Выведем имя пользователя, используя одну из этих переменных, а в случае если все они не определены, то покажем "Аноним". |
| 46 | +
|
| 47 | +Для этого воспользуемся оператором `??`: |
| 48 | +
|
| 49 | +```js run |
| 50 | +let firstName = null; |
| 51 | +let lastName = null; |
| 52 | +let nickName = "Суперкодер"; |
| 53 | + |
| 54 | +// показывает первое определённое значение: |
| 55 | +*!* |
| 56 | +alert(firstName ?? lastName ?? nickName ?? "Аноним"); // Суперкодер |
| 57 | +*/!* |
| 58 | +``` |
| 59 | +
|
| 60 | +## Сравнение с || |
| 61 | +
|
| 62 | +Оператор ИЛИ `||` можно использовать для того же, что и `??`, как это было показано в [предыдущей главе](info:logical-operators#or-finds-the-first-truthy-value). |
| 63 | +
|
| 64 | +Например, если в приведённом выше коде заменить `??` на `||`, то будет тот же самый результат: |
| 65 | +
|
| 66 | +```js run |
| 67 | +let firstName = null; |
| 68 | +let lastName = null; |
| 69 | +let nickName = "Суперкодер"; |
| 70 | + |
| 71 | +// показывает первое истинное значение: |
| 72 | +*!* |
| 73 | +alert(firstName || lastName || nickName || "Аноним"); // Суперкодер |
| 74 | +*/!* |
| 75 | +``` |
| 76 | +
|
| 77 | +Оператор ИЛИ `||` существует с самого появления JavaScript, поэтому ранее для решения похожих задач разработчики использовали именно его. |
| 78 | +
|
| 79 | +С другой стороны, сравнительно недавно в язык был добавлен оператор объединения с null `??` как раз потому, что многие были недовольны оператором `||`. |
| 80 | +
|
| 81 | +Важное различие между ними заключается в том, что: |
| 82 | +- `||` возвращает первое *истинное* значение. |
| 83 | +- `??` возвращает первое *определённое* значение. |
| 84 | +
|
| 85 | +Проще говоря, оператор `||` не различает `false`, `0`, пустую строку `""` и `null/undefined`. Для него они все одинаковые, т.е. являются ложными значениями. Если первым аргументом для оператора `||` будет любое из перечисленных значений, то в качестве результата мы получим второй аргумент. |
| 86 | +
|
| 87 | +Однако на практике часто требуется использовать значение по умолчанию только тогда, когда переменная является `null/undefined`. Ведь именно тогда значение действительно неизвестно/не определено. |
| 88 | +
|
| 89 | +Например, рассмотрим следующий пример: |
| 90 | +
|
| 91 | +```js run |
| 92 | +let height = 0; |
| 93 | + |
| 94 | +alert(height || 100); // 100 |
| 95 | +alert(height ?? 100); // 0 |
| 96 | +``` |
| 97 | +
|
| 98 | +- `height || 100` проверяет, имеет ли переменная `height` ложное значение, что так и есть, |
| 99 | + - поэтому результатом является второй аргумент, т.е. `100`. |
| 100 | +- `height ?? 100` проверяет, что переменная `height` содержит `null/undefined`, а поскольку это не так, |
| 101 | + - то результатом является сама переменная `height`, т.е. `0`. |
| 102 | +
|
| 103 | +Если нулевая высота является "нормальным" значением, которое не должно заменяться значением по умолчанию, то оператор `??` делает как раз то, что нужно. |
| 104 | +
|
| 105 | +## Приоритет |
| 106 | +
|
| 107 | +Оператор `??` имеет довольно низкий приоритет: `5`, согласно [таблице на MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table). |
| 108 | +Таким образом, оператор `??` вычисляется до `=` и `?`, но после большинства других операций, таких как `+`, `*`. |
| 109 | +
|
| 110 | +Из этого следует, что если нужно выбрать значение при помощи оператора `??` вместе с другими операторами в выражении, следует добавить круглые скобки: |
| 111 | +
|
| 112 | +```js run |
| 113 | +let height = null; |
| 114 | +let width = null; |
| 115 | + |
| 116 | +// важно: используйте круглые скобки |
| 117 | +let area = (height ?? 100) * (width ?? 50); |
| 118 | + |
| 119 | +alert(area); // 5000 |
| 120 | +``` |
| 121 | +
|
| 122 | +Иначе, если опустить скобки, то оператор `*` выполнится первым, так как у него приоритет выше, чем у `??`, а это приведёт к неправильным результатам. |
| 123 | +
|
| 124 | +```js |
| 125 | +// без круглых скобок |
| 126 | +let area = height ?? 100 * width ?? 50; |
| 127 | + |
| 128 | +// ...то же самое, что предыдущее выражение (вероятно, это не то, что нам нужно): |
| 129 | +let area = height ?? (100 * width) ?? 50; |
| 130 | +``` |
| 131 | +
|
| 132 | +### Использование ?? вместе с && или || |
| 133 | +
|
| 134 | +По соображениям безопасности JavaScript запрещает использование оператора `??` вместе с `&&` и `||`, если только приоритет явно не указан в круглых скобках. |
| 135 | +
|
| 136 | +Выполнение следующего кода приведёт к синтаксической ошибке: |
| 137 | +
|
| 138 | +```js run |
| 139 | +let x = 1 && 2 ?? 3; // Синтаксическая ошибка |
| 140 | +``` |
| 141 | +
|
| 142 | +Это довольно спорное ограничение, которое было описано в спецификации языка, чтобы избежать ошибок при замене оператора `||` на `??`. |
| 143 | +
|
| 144 | +Используйте круглые скобки, чтобы обойти это ограничение: |
| 145 | +
|
| 146 | +```js run |
| 147 | +*!* |
| 148 | +let x = (1 && 2) ?? 3; // Работает без ошибок |
| 149 | +*/!* |
| 150 | + |
| 151 | +alert(x); // 2 |
| 152 | +``` |
| 153 | +
|
| 154 | +## Итого |
| 155 | +
|
| 156 | +- Оператор объединения с null `??` — это быстрый способ выбрать первое "определённое" значение из списка. |
| 157 | +
|
| 158 | + Используется для присвоения переменным значений по умолчанию: |
| 159 | +
|
| 160 | + ```js |
| 161 | + // будет height=100, если переменная height равна null или undefined |
| 162 | + height = height ?? 100; |
| 163 | + ``` |
| 164 | +
|
| 165 | +- Оператор `??` имеет очень низкий приоритет, лишь немного выше, чем у `?` и `=`, поэтому при использовании его в выражении, скорее всего, потребуются скобки. |
| 166 | +- Запрещено использовать вместе с `||` или `&&` без явно указанных круглых скобок. |
0 commit comments