By Dmitri Pavlutin
Translation: Crazy geek
Original text: dmitripavlutin.com/javascript-…
Reproduced without permission
JavaScript features dramatically change the way you code. Since ES2015, the features that have had the most impact on my code are deconstruction, arrow functions, classes, and module systems.
As of August 2019, a new proposal optional Chaining reached phase 3, which will be a nice improvement. Optional Chaining changes the way properties are accessed from deep object structures.
Let’s look at how optional chaining simplifies code by removing boilerplate conditions and variables when deep accessing properties that might be missing.
Problem 1.
Due to the dynamic nature of JavaScript, objects can have very different nested object structures.
In general, you deal with such objects when:
-
Get remote JSON data
-
Using configuration Objects
-
It’s optional
While this gives objects the flexibility to support different structured data, it adds complexity when accessing the properties of these objects.
BigObject can have different sets of properties at runtime:
// One version of bigObject
const bigObject = {
// ...
prop1: {
/ /...
prop2: {
// ...
value: 'Some value'}}};// Other version of bigObject
const bigObject = {
// ...
prop1: {
// Nothing here }};Copy the code
Therefore, you must manually check if the attribute exists:
// Later
if(bigObject && bigObject.prop1 ! =null&& bigObject.prop1.prop2 ! =null) {
let result = bigObject.prop1.prop2.value;
}
Copy the code
This produces a lot of boilerplate code. It would be nice if we didn’t have to write this code.
Let’s see how optional chaining solves this problem and reduces boilerplate conditions.
2. Easy in-depth access to properties
Let’s design an object that holds movie information. This object contains a title property and optional director and Actors.
The movieSmall object only contains title, while the movieFull object contains the full set of properties:
const movieSmall = {
title: 'Heat'
};
const movieFull = {
title: 'Blade Runner'.director: { name: 'Ridley Scott' },
actors: [{ name: 'Harrison Ford' }, { name: 'Rutger Hauer'}};Copy the code
Let’s write a function that gets the director’s name. Keep in mind that the director attribute may not exist:
function getDirector(movie) {
if(movie.director ! =null) {
return movie.director.name;
}
}
getDirector(movieSmall); // => undefined
getDirector(movieFull); // => 'Ridley Scott'
Copy the code
if (movie.director) {… The} condition is used to verify that the director property is defined. Without this precaution, JavaScript will throw TypeError: Cannot read property ‘name’ of undefined when accessing movieSmall object director.
This is the right place to use the new optional chaining feature and remove the existence validation of movie.director. The new version of getDirector() looks much shorter:
function getDirector(movie) {
returnmovie.director? .name; } getDirector(movieSmall);// => undefined
getDirector(movieFull); // => 'Ridley Scott'
Copy the code
In the expression movie.director? In.name you can find the?. : optional chaining operator.
In the case of movieSmall, if the property director is missing. The movie. The director? The value of. Name is undefined. The optional chaining operator prevents raising TypeError:Cannot read property ‘name’ of undefined.
Conversely, in the case of movieFull, the property director is available. movie.director? The.name value is ‘Ridley Scott’.
In a nutshell, the code snippet:
letname = movie.director? .name;Copy the code
Is equivalent to:
let name;
if(movie.director ! =null) {
name = movie.director.name;
}
Copy the code
? . Simplifies the getDirector() function by eliminating 2 lines of code. This is why I like optional Chaining.
Item 2.1 array
But the optional chaining feature can do more. You can use multiple optional chaining operators in the same expression. You can even use it to securely access array items!
The next task is to write a function that returns the name of the movie’s main character.
In movie objects, the Actors array can be empty or even missing, so you must add other conditions:
function getLeadingActor(movie) {
if (movie.actors && movie.actors.length > 0) {
return movie.actors[0].name;
}
}
getLeadingActor(movieSmall); // => undefined
getLeadingActor(movieFull); // => 'Harrison Ford'
Copy the code
if (movie.actors && movies.actors.length > 0) {… The} condition needs to ensure that movie contains the Actors property and that this property has at least one actor.
This task is easily solved by using optional chaining:
function getLeadingActor(movie) {
returnmovie.actors? .0]? .name; } getLeadingActor(movieSmall);// => undefined
getLeadingActor(movieFull); // => 'Harrison Ford'
Copy the code
actors? . Ensure that the Actors attribute exists. [0]? . Make sure the first actor exists in the list. Very good!
3. Nullish merge
A new proposal called Nullish Coalescing Operator proposes using?? Handle undefined or NULL and default them to a specific value.
If variable is undefined or null, the expression variable?? The result of defaultValue is defaultValue, otherwise the expression is the value of variable.
const noValue = undefined;
const value = 'Hello';
noValue ?? 'Nothing'; // => 'Nothing'
value ?? 'Nothing'; // => 'Hello'
Copy the code
Nullish merge can improve optional chaining by default when evaluated as undefined.
For example, when there is no actor in the movie object, let’s change getLeading() to return “Unknown actor” :
function getLeadingActor(movie) {
returnmovie.actors? .0]? .name ??'Unknown actor';
}
getLeadingActor(movieSmall); // => 'Unknown actor'
getLeadingActor(movieFull); // => 'Harrison Ford'
Copy the code
4. 3 forms of optional chaining
You can use optional chaining in the following three forms.
The first form object? .property is used to access static properties:
const object = null; object? .property;// => undefined
Copy the code
The second form object? .[expression] Is used to access dynamic properties or array items:
const object = null;
const name = 'property'; object? .[name];// => undefined
const array = null; array? .0]; // => undefined
Copy the code
And finally, the third form object? .([arg1,[arg2,…]]) Executes an object method:
const object = null; object? .method('Some value'); // => undefined
Copy the code
If desired, you can create long optional chains by combining these forms:
constvalue = object.maybeUndefinedProp? .maybeNull()? .[propName];Copy the code
5. Short circuit: stop atnull/undefined
The interesting thing about the optional chaining operator is that as long as the leftHandSide? If an invalid value is encountered in.righthandSide, the evaluation of the right accessor stops. This is called a short circuit.
Let’s look at an example:
const nothing = null;
let index = 0; nothing? .[index++];// => undefined
index; / / = > 0
Copy the code
Nothing maintains a nullish value, so the optional chaining evaluation is undefined and skips the evaluation of the right accessor. Because the index number doesn’t increase.
6. When to use optional chaining
Resist the urge to use the optional chaining operator to access any type of attribute: this can lead to misleading use. The next section shows when to use it correctly.
6.1 Accessing properties that may not be valid
? . Must only be used near attributes that may not be valid: maybeNullish? Prop. In other cases, use the old property accessors:.property or [propExpression].
Think back to the Movie object. View the expression movie.director? .name. Because director can be undefined, it is correct to use the optional chaining operator near the director attribute.
Instead, use? It makes no sense to access the movie title: Movie? The title. Movie objects are not invalid.
// Good
function logMovie(movie) {
console.log(movie.director? .name);console.log(movie.title);
}
// Bad
function logMovie(movie) {
// director needs optional chaining
console.log(movie.director.name);
// movie doesn't need optional chaining
console.log(movie? .title); }Copy the code
6.2 There are usually better options
The following function hasPadding() accepts a style object with an optional padding property. Padding has optional attributes left, top, right, and bottom.
Try using the optional chaining operator:
function hasPadding({ padding }) {
consttop = padding? .top ??0;
constright = padding? .right ??0;
constbottom = padding? .bottom ??0;
constleft = padding? .left ??0;
returnleft + top + right + bottom ! = =0;
}
hasPadding({ color: 'black' }); // => false
hasPadding({ padding: { left: 0}});// => false
hasPadding({ padding: { right: 10 }}); // => true
Copy the code
Although the function correctly determines if the element has a fill, it is very difficult to use optional chaining for every attribute.
A better approach is to use the object extension operator to populate the object with a default value of zero:
function hasPadding({ padding }) {
const p = {
top: 0.right: 0.bottom: 0.left: 0. padding };returnp.top + p.left + p.right + p.bottom ! = =0;
}
hasPadding({ color: 'black' }); // => false
hasPadding({ padding: { left: 0}});// => false
hasPadding({ padding: { right: 10 }}); // => true
Copy the code
In my opinion, this version of hasPadding() is easier to read.
7. Why do I like it?
I like the optional chaining operator because it allows easy access to properties from nested objects. It reduces the effort of writing boilerplate files to validate invalid values on each property accessor from the accessor chain.
When you combine optional chaining with invalid merge operators, you get better results and can handle defaults more easily.