Skip to content

Commit 624fd8a

Browse files
committed
refactor objects, add optional chaining
0 parents  commit 624fd8a

File tree

1 file changed

+176
-0
lines changed

1 file changed

+176
-0
lines changed

article.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
2+
# Optional chaining '?.'
3+
4+
[recent browser="new"]
5+
6+
The optional chaining `?.` is an error-prone way to access nested object properties, even if an intermediate property doesn't exist.
7+
8+
## The problem
9+
10+
If you've just started to read the tutorial and learn JavaScript, maybe the problem hasn't touched you yet, but it's quite common.
11+
12+
For example, some of our users have addresses, but few did not provide them. Then we can't safely read `user.address.street`:
13+
14+
```js run
15+
let user = {}; // the user happens to be without address
16+
17+
alert(user.address.street); // Error!
18+
```
19+
20+
Or, in the web development, we'd like to get an information about an element on the page, but it may not exist:
21+
22+
```js run
23+
// Error if the result of querySelector(...) is null
24+
let html = document.querySelector('.my-element').innerHTML;
25+
```
26+
27+
Before `?.` appeared in the language, the `&&` operator was used to work around that.
28+
29+
For example:
30+
31+
```js run
32+
let user = {}; // user has no address
33+
34+
alert( user && user.address && user.address.street ); // undefined (no error)
35+
```
36+
37+
AND'ing the whole path to the property ensures that all components exist, but is cumbersome to write.
38+
39+
## Optional chaining
40+
41+
The optional chaining `?.` stops the evaluation and returns `undefined` if the part before `?.` is `undefined` or `null`.
42+
43+
Further in this article, for brewity, we'll be saying that something "exists" if it's not `null` and not `undefined`.
44+
45+
46+
Here's the safe way to access `user.address.street`:
47+
48+
```js run
49+
let user = {}; // user has no address
50+
51+
alert( user?.address?.street ); // undefined (no error)
52+
```
53+
54+
Reading the address with `user?.address` works even if `user` object doesn't exist:
55+
56+
```js run
57+
let user = null;
58+
59+
alert( user?.address ); // undefined
60+
61+
alert( user?.address.street ); // undefined
62+
alert( user?.address.street.anything ); // undefined
63+
```
64+
65+
Please note: the `?.` syntax works exactly where it's placed, not any further.
66+
67+
In the last two lines the evaluation stops immediately after `user?.`, never accessing further properties. But if the `user` actually exists, then the further intermediate properties, such as `user.address` must exist.
68+
69+
```warn header="Don't overuse the optional chaining"
70+
We should use `?.` only where it's ok that something doesn't exist.
71+
72+
For example, if according to our coding logic `user` object must be there, but `address` is optional, then `user.address?.street` would be better.
73+
74+
So, if `user` happens to be undefined due to a mistake, we'll know about it and fix it. Otherwise, coding errors can be silenced where not appropriate, and become more difficult to debug.
75+
```
76+
77+
````warn header="The variable before `?.` must exist"
78+
If there's no variable `user`, then `user?.anything` triggers an error:
79+
80+
```js run
81+
// ReferenceError: user is not defined
82+
user?.address;
83+
```
84+
The optional chaining only tests for `null/undefined`, doesn't interfere with any other language mechanics.
85+
````
86+
87+
## Short-circuiting
88+
89+
As it was said before, the `?.` immediately stops ("short-circuits") the evaluation if the left part doesn't exist.
90+
91+
So, if there are any further function calls or side effects, they don't occur:
92+
93+
```js run
94+
let user = null;
95+
let x = 0;
96+
97+
user?.sayHi(x++); // nothing happens
98+
99+
alert(x); // 0, value not incremented
100+
```
101+
102+
## Other cases: ?.(), ?.[]
103+
104+
The optional chaining `?.` is not an operator, but a special syntax construct, that also works with functions and square brackets.
105+
106+
For example, `?.()` is used to call a function that may not exist.
107+
108+
In the code below, some of our users have `admin` method, and some don't:
109+
110+
```js run
111+
let user1 = {
112+
admin() {
113+
alert("I am admin");
114+
}
115+
}
116+
117+
let user2 = {};
118+
119+
*!*
120+
user1.admin?.(); // I am admin
121+
user2.admin?.();
122+
*/!*
123+
```
124+
125+
Here, in both lines we first use the dot `.` to get `admin` property, because the user object must exist, so it's safe read from it.
126+
127+
Then `?.()` checks the left part: if the user exists, then it runs (for `user1`). Otherwise (for `user2`) the evaluation stops without errors.
128+
129+
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.
130+
131+
```js run
132+
let user1 = {
133+
firstName: "John"
134+
};
135+
136+
let user2 = null; // Imagine, we couldn't authorize the user
137+
138+
let key = "firstName";
139+
140+
alert( user1?.[key] ); // John
141+
alert( user2?.[key] ); // undefined
142+
143+
alert( user1?.[key]?.something?.not?.existing); // undefined
144+
```
145+
146+
Also we can use `?.` with `delete`:
147+
148+
```js run
149+
delete user?.name; // delete user.name if user exists
150+
```
151+
152+
```warn header="We can use `?.` for safe reading and deleting, but not writing"
153+
The optional chaining `?.` has no use at the left side of an assignment:
154+
155+
```js run
156+
// the idea of the code below is to write user.name, if user exists
157+
158+
user?.name = "John"; // Error, doesn't work
159+
// because it evaluates to undefined = "John"
160+
```
161+
162+
## Summary
163+
164+
The `?.` syntax has three forms:
165+
166+
1. `obj?.prop` -- returns `obj.prop` if `obj` exists, otherwise `undefined`.
167+
2. `obj?.[prop]` -- returns `obj[prop]` if `obj` exists, otherwise `undefined`.
168+
3. `obj?.method()` -- calls `obj.method()` if `obj` exists, otherwise returns `undefined`.
169+
170+
As we can see, all of them are straightforward and simple to use. The `?.` checks the left part for `null/undefined` and allows the evaluation to proceed if it's not so.
171+
172+
A chain of `?.` allows to safely access nested properties.
173+
174+
Still, we should apply `?.` carefully, only where it's ok that the left part doesn't to exist.
175+
176+
So that it won't hide programming errors from us, if they occur.

0 commit comments

Comments
 (0)