Skip to content

Commit c797c2b

Browse files
committed
Update "Optional chaining" English article
1 parent bcb1fb6 commit c797c2b

File tree

1 file changed

+30
-17
lines changed
  • 1-js/04-object-basics/07-optional-chaining

1 file changed

+30
-17
lines changed

1-js/04-object-basics/07-optional-chaining/article.md

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ In many practical cases we'd prefer to get `undefined` instead of an error here
3232
let html = document.querySelector('.elem').innerHTML; // error if it's null
3333
```
3434

35-
Once again, if the element doesn't exist, we'll get an error accessing `.innerHTML` of `null`. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept `html = null` as the result.
35+
Once again, if the element doesn't exist, we'll get an error accessing `.innerHTML` property of `null`. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept `html = null` as the result.
3636

3737
How can we do this?
3838

@@ -44,11 +44,19 @@ let user = {};
4444
alert(user.address ? user.address.street : undefined);
4545
```
4646

47-
It works, there's no error... But it's quite inelegant. As you can see, the `"user.address"` appears twice in the code. For more deeply nested properties, that becomes a problem as more repetitions are required.
47+
It works, there's no error... But it's quite inelegant. As you can see, the `"user.address"` appears twice in the code.
4848

49-
E.g. let's try getting `user.address.street.name`.
49+
Here's how the same would look for `document.querySelector`:
5050

51-
We need to check both `user.address` and `user.address.street`:
51+
```js run
52+
let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null;
53+
```
54+
55+
We can see that the element search `document.querySelector('.elem')` is actually called twice here. Not good.
56+
57+
For more deeply nested properties, it becomes even uglier, as more repetitions are required.
58+
59+
E.g. let's get `user.address.street.name` in a similar fashion.
5260

5361
```js
5462
let user = {}; // user has no address
@@ -58,7 +66,7 @@ alert(user.address ? user.address.street ? user.address.street.name : null : nul
5866

5967
That's just awful, one may even have problems understanding such code.
6068

61-
Don't even care to, as there's a better way to write it, using the `&&` operator:
69+
There's a little better way to write it, using the `&&` operator:
6270

6371
```js run
6472
let user = {}; // user has no address
@@ -92,6 +100,12 @@ alert( user?.address?.street ); // undefined (no error)
92100
93101
The code is short and clean, there's no duplication at all.
94102
103+
Here's an example with `document.querySelector`:
104+
105+
```js run
106+
let html = document.querySelector('.elem')?.innerHTML; // will be undefined, if there's no element
107+
```
108+
95109
Reading the address with `user?.address` works even if `user` object doesn't exist:
96110
97111
```js run
@@ -108,9 +122,9 @@ E.g. in `user?.address.street.name` the `?.` allows `user` to safely be `null/un
108122
```warn header="Don't overuse the optional chaining"
109123
We should use `?.` only where it's ok that something doesn't exist.
110124

111-
For example, if according to our coding logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`.
125+
For example, if according to our code logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`.
112126

113-
So, if `user` happens to be undefined due to a mistake, we'll see a programming error about it and fix it. Otherwise, coding errors can be silenced where not appropriate, and become more difficult to debug.
127+
Then, if `user` happens to be undefined, we'll see a programming error about it and fix it. Otherwise, if we overuse `?.`, coding errors can be silenced where not appropriate, and become more difficult to debug.
114128
```
115129
116130
````warn header="The variable before `?.` must be declared"
@@ -127,15 +141,15 @@ The variable must be declared (e.g. `let/const/var user` or as a function parame
127141

128142
As it was said before, the `?.` immediately stops ("short-circuits") the evaluation if the left part doesn't exist.
129143

130-
So, if there are any further function calls or side effects, they don't occur.
144+
So, if there are any further function calls or operations to the right of `?.`, they won't be made.
131145

132146
For instance:
133147

134148
```js run
135149
let user = null;
136150
let x = 0;
137151

138-
user?.sayHi(x++); // no "sayHi", so the execution doesn't reach x++
152+
user?.sayHi(x++); // no "user", so the execution doesn't reach sayHi call and x++
139153

140154
alert(x); // 0, value not incremented
141155
```
@@ -162,13 +176,13 @@ userAdmin.admin?.(); // I am admin
162176
*/!*
163177

164178
*!*
165-
userGuest.admin?.(); // nothing (no such method)
179+
userGuest.admin?.(); // nothing happens (no such method)
166180
*/!*
167181
```
168182
169-
Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the user object exists, so it's safe read from it.
183+
Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the `user` object exists, so it's safe read from it.
170184
171-
Then `?.()` checks the left part: if the admin function exists, then it runs (that's so for `userAdmin`). Otherwise (for `userGuest`) the evaluation stops without errors.
185+
Then `?.()` checks the left part: if the `admin` function exists, then it runs (that's so for `userAdmin`). Otherwise (for `userGuest`) the evaluation stops without errors.
172186
173187
The `?.[]` syntax also works, if we'd like to use brackets `[]` to access properties instead of dot `.`. Similar to previous cases, it allows to safely read a property from an object that may not exist.
174188
@@ -179,7 +193,7 @@ let user1 = {
179193
firstName: "John"
180194
};
181195

182-
let user2 = null;
196+
let user2 = null;
183197

184198
alert( user1?.[key] ); // John
185199
alert( user2?.[key] ); // undefined
@@ -192,17 +206,16 @@ delete user?.name; // delete user.name if user exists
192206
```
193207
194208
````warn header="We can use `?.` for safe reading and deleting, but not writing"
195-
The optional chaining `?.` has no use at the left side of an assignment.
209+
The optional chaining `?.` has no use on the left side of an assignment.
196210
197211
For example:
198212
```js run
199213
let user = null;
200214

201215
user?.name = "John"; // Error, doesn't work
202-
// because it evaluates to undefined = "John"
216+
// because it evaluates to: undefined = "John"
203217
```
204218
205-
It's just not that smart.
206219
````
207220
208221
## Summary
@@ -217,4 +230,4 @@ As we can see, all of them are straightforward and simple to use. The `?.` check
217230
218231
A chain of `?.` allows to safely access nested properties.
219232
220-
Still, we should apply `?.` carefully, only where it's acceptable that the left part doesn't exist. So that it won't hide programming errors from us, if they occur.
233+
Still, we should apply `?.` carefully, only where it's acceptable, according to our code logic, that the left part doesn't exist. So that it won't hide programming errors from us, if they occur.

0 commit comments

Comments
 (0)