From 5c388dd791f7d37a6bd221917138c7a3f9dd1c51 Mon Sep 17 00:00:00 2001 From: Ilya Kantor Date: Wed, 23 Sep 2020 12:00:09 +0300 Subject: [PATCH] content --- .../12-nullish-coalescing-operator/article.md | 110 ++++++++++++------ 1 file changed, 74 insertions(+), 36 deletions(-) diff --git a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md index c72dd91d..434edb87 100644 --- a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md +++ b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md @@ -2,32 +2,57 @@ [recent browser="new"] -The nullish coalescing operator `??` provides a short syntax for selecting a first "defined" variable from the list. +Here, in this article, we'll say that an expression is "defined" when it's neither `null` nor `undefined`. + +The nullish coalescing operator is written as two question marks `??`. The result of `a ?? b` is: -- `a` if it's not `null` or `undefined`, -- `b`, otherwise. +- if `a` is defined, then `a`, +- if `a` isn't defined, then `b`. -So, `x = a ?? b` is a short equivalent to: + +In other words, `??` returns the first argument if it's defined. Otherwise, the second one. + +The nullish coalescing operator isn't anything completely new. It's just a nice syntax to get the first "defined" value of the two. + +We can rewrite `result = a ?? b` using the operators that we already know, like this: ```js -x = (a !== null && a !== undefined) ? a : b; +result = (a !== null && a !== undefined) ? a : b; ``` -Here's a longer example. +The common use case for `??` is to provide a default value for a potentially undefined variable. -Imagine, we have a user, and there are variables `firstName`, `lastName` or `nickName` for their first name, last name and the nick name. All of them may be undefined, if the user decided not to enter any value. +For example, here we show `Anonymous` if `user` isn't defined: -We'd like to display the user name: one of these three variables, or show "Anonymous" if nothing is set. +```js run +let user; -Let's use the `??` operator to select the first defined one: +alert(user ?? "Anonymous"); // Anonymous +``` + +Of course, if `user` had any value except `null/undefined`, then we would see it instead: + +```js run +let user = "John"; + +alert(user ?? "Anonymous"); // John +``` + +We can also use a sequence of `??` to select the first defined value from a list. + +Let's say we a user's data in variables `firstName`, `lastName` or `nickName`. All of them may be undefined, if the user decided not to enter a value. + +We'd like to display the user name using one of these variables, or show "Anonymous" if all of them are undefined. + +Let's use the `??` operator for that: ```js run let firstName = null; let lastName = null; let nickName = "Supercoder"; -// show the first not-null/undefined value +// shows the first defined value: *!* alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder */!* @@ -35,24 +60,35 @@ alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder ## Comparison with || -The OR `||` operator can be used in the same way as `??`. Actually, we can replace `??` with `||` in the code above and get the same result, as it was described in the [previous chapter](info:logical-operators#or-finds-the-first-truthy-value). +The OR `||` operator can be used in the same way as `??`, as it was described in the [previous chapter](info:logical-operators#or-finds-the-first-truthy-value). -The important difference is that: +For example, in the code above we could replace `??` with `||` and still get the same result: + +```js run +let firstName = null; +let lastName = null; +let nickName = "Supercoder"; + +// shows the first truthy value: +*!* +alert(firstName || lastName || nickName || "Anonymous"); // Supercoder +*/!* +``` + +The OR `||` operator exists since the beginning of JavaScript, so developers were using it for such purposes for a long time. + +On the other hand, the nullish coalescing operator `??` was added only recently, and the reason for that was that people weren't quite happy with `||`. + +The subtle, yet important difference is that: - `||` returns the first *truthy* value. - `??` returns the first *defined* value. -This matters a lot when we'd like to treat `null/undefined` differently from `0`. +In other words, `||` doesn't distinguish between `false`, `0`, an empty string `""` and `null/undefined`. They are all the same -- falsy values. If any of these is the first argument of `||`, then we'll get the second argument as the result. + +In practice though, we may want to use default value only when the variable is `null/undefined`. That is, when the value is really unknown/not set. For example, consider this: -```js -height = height ?? 100; -``` - -This sets `height` to `100` if it's not defined. - -Let's compare it with `||`: - ```js run let height = 0; @@ -60,19 +96,20 @@ alert(height || 100); // 100 alert(height ?? 100); // 0 ``` -Here, `height || 100` treats zero height as unset, same as `null`, `undefined` or any other falsy value. So the result is `100`. +Here, we have a zero height. -The `height ?? 100` returns `100` only if `height` is exactly `null` or `undefined`. So the `alert` shows the height value `0` "as is". +- The `height || 100` checks `height` for being a falsy value, and it really is. + - so the result is the second argument, `100`. +- The `height ?? 100` checks `height` for being `null/undefined`, and it's not, + - so the result is `height` "as is", that is `0`. -Which behavior is better depends on a particular use case. When zero height is a valid value, then `??` is preferrable. +If we assume that zero height is a valid value, that shouldn't be replaced with the default, then `??` does just the right thing. ## Precedence -The precedence of the `??` operator is rather low: `5` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table). +The precedence of the `??` operator is rather low: `5` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table). So `??` is evaluated before `=` and `?`, but after most other operations, such as `+`, `*`. -So `??` is evaluated after most other operations, but before `=` and `?`. - -If we need to choose a value with `??` in a complex expression, then consider adding parentheses: +So if we'd like to choose a value with `??` an expression with other operators, consider adding parentheses: ```js run let height = null; @@ -84,18 +121,19 @@ let area = (height ?? 100) * (width ?? 50); alert(area); // 5000 ``` -Otherwise, if we omit parentheses, `*` has the higher precedence than `??` and would run first. - -That would work be the same as: +Otherwise, if we omit parentheses, then as `*` has the higher precedence than `??`, it would execute first, leading to incorrect results. ```js -// probably not correct +// without parentheses +let area = height ?? 100 * width ?? 50; + +// ...works the same as this (probably not what we want): let area = height ?? (100 * width) ?? 50; ``` -There's also a related language-level limitation. +### Using ?? with && or || -**Due to safety reasons, it's forbidden to use `??` together with `&&` and `||` operators.** +Due to safety reasons, JavaScript forbids using `??` together with `&&` and `||` operators, unless the precedence is explicitly specified with parentheses. The code below triggers a syntax error: @@ -103,7 +141,7 @@ The code below triggers a syntax error: let x = 1 && 2 ?? 3; // Syntax error ``` -The limitation is surely debatable, but it was added to the language specification with the purpose to avoid programming mistakes, as people start to switch to `??` from `||`. +The limitation is surely debatable, but it was added to the language specification with the purpose to avoid programming mistakes, when people start to switch to `??` from `||`. Use explicit parentheses to work around it: @@ -126,5 +164,5 @@ alert(x); // 2 height = height ?? 100; ``` -- The operator `??` has a very low precedence, a bit higher than `?` and `=`. +- The operator `??` has a very low precedence, a bit higher than `?` and `=`, so consider adding parentheses when using it in an expression. - It's forbidden to use it with `||` or `&&` without explicit parentheses.