Skip to content

Commit e7710c0

Browse files
committed
Finish destructuring chapter
1 parent 1bbd472 commit e7710c0

File tree

1 file changed

+138
-44
lines changed

1 file changed

+138
-44
lines changed

manuscript/05-Destructuring.md

Lines changed: 138 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,25 @@ console.log(name); // "foo"
7575

7676
In this example, `type` and `name` are initialized with values when declared. The next line uses destructuring assignment to change those values by reading from `node`. Note that you must put parentheses around a destructuring assignment statement. That's because an opening curly brace is expected to a be a block statement, and a block statement cannot appear on the left side of an assignment. The parentheses signal that the next curly brace is not a block statement and should be interpreted as an expression, allowing the assignment to complete.
7777

78+
Destructuring assignment is a bit more flexible than destructuring declarations, as it's possible to store values into object properties. Here's an example:
79+
80+
```js
81+
let node = {
82+
type: "Identifier",
83+
name: "foo"
84+
},
85+
anotherNode = {};
86+
87+
({ anotherNode.type, anotherNode.name } = node);
88+
89+
console.log(anotherNode.type); // "Identifier"
90+
console.log(anotherNode.name); // "foo"
91+
```
92+
93+
This code specifies `anotherNode.type` and `anotherNode.name` as the locations in which to store the destructured information. Note that `anotherNode` is initially declared without any properties, but that's unimportant because properties can be added to `anotherNode` at any point in time. The end result is that two new properties are added to `anotherNode` through destructuring assignment.
94+
95+
W> An error is thrown when the right side of the destructured assignment expression (the expression after `=`) evaluates to `null` or `undefined`. This happens because any attempt to read a property of `null` or `undefined` results in a runtime error.
96+
7897
#### Default Values
7998

8099
If you specify a property name that doesn't exist on the object, then the local variable is assigned a value of `undefined`. For example:
@@ -203,6 +222,17 @@ In this version of the code, `node.loc.start` is stored in a new local variable
203222

204223
While object destructuring is very powerful and has a lot of options, array destructuring offers some unique capabilities that allow you to extract information from arrays.
205224

225+
A> #### Syntax Gotcha
226+
A>
227+
A>Be careful when using nested destructuring because you an inadvertently create a statement that has no effect. Empty curly braces are legal in object destructuring, however, they don't do anything. For example:
228+
A>
229+
A>```js
230+
A>// no variables declared!
231+
A>let { loc: {} } = node;
232+
A>```
233+
A>
234+
A>There are no bindings declared in this statement. Due to the curly braces on the right, `loc` is used as a location to inspect rather than a binding to create. In such a case, it's likely that the intent was to use `=` to define a default value rather than `:` to define a location. It's possible that this syntax will be made illegal in the future, but for now, this is a bit of a gotcha to look out for.
235+
206236
### Array Destructuring
207237

208238
Array destructuring works in a way that is very similar to object destructuring, with the exception that it uses array literal syntax instead of object literal syntax. The destructuring operates on positions within an array rather than the named properties that are available in objects. For example:
@@ -280,6 +310,8 @@ console.log(b); // 1
280310

281311
The array destructuring assignment in this example looks like a mirror image. The left side of the assignment (before the equals sign) is the destructuring pattern just like you've seen in the previous examples. The right side is an array literal that is temporarily created for the purposes of doing the swap. The destructuring happens on the temporary array, which has the values of `b` and `a` copied into its first and second positions. The effect is that the variables have swapped values.
282312

313+
W> As with object destructuring assignment, an error is thrown when the right side of the destructured assignment expression evaluates to `null` or `undefined`.
314+
283315
#### Default Values
284316

285317
Another similarity with object destructuring is the ability to specify a default value for any position in the array. The default value is used when the given position either doesn't exist or has the value `undefined`. For example:
@@ -351,55 +383,47 @@ console.log(clonedColors); //"[red,green,blue]"
351383

352384
In this example, rest items are used to copy values from `colors` into `clonedColors`. While it's a matter of perception as to whether this or `concat()` makes the developer's intent clearer, this is a useful ability to be aware of.
353385

354-
386+
W> Rest items must be the last entry in the destructured array and cannot be followed by a comma. Including a comma after rest items is a syntax error.
355387

356388
## Mixed Destructuring
357389

358-
It's possible to mix objects and arrays together in a destructuring assignment expression using a mix of object and array literals. For example:
390+
Object and array destructuring can be used together to create more complex expressions. In doing so, you are able to extract just the pieces of information you want from any mixture of objects and arrays. For example:
359391

360392
```js
361-
let options = {
362-
repeat: true,
363-
save: false,
364-
colors: [ "red", "green", "blue" ]
365-
};
366-
367-
let { repeat, save, colors: [ firstColor, secondColor ]} = options;
368-
369-
console.log(repeat); // true
370-
console.log(save); // false
371-
console.log(firstColor); // "red"
372-
console.log(secondColor); // "green"
373-
```
374-
375-
This example extracts two property values, `repeat` and `save`, and then two items from the `colors` array, `firstColor` and `secondColor`. Of course, you could also choose to retrieve the entire array:
376-
377-
```js
378-
let options = {
379-
repeat: true,
380-
save: false,
381-
colors: [ "red", "green", "blue" ]
393+
let node = {
394+
type: "Identifier",
395+
name: "foo",
396+
loc: {
397+
start: {
398+
line: 1,
399+
column: 1
400+
},
401+
end: {
402+
line: 1,
403+
column: 4
404+
}
405+
},
406+
range: [0, 3]
382407
};
383408

384-
let { repeat, save, colors } = options;
409+
let {
410+
loc: { start },
411+
range: [ startIndex ]
412+
} = node;
385413

386-
console.log(repeat); // true
387-
console.log(save); // false
388-
console.log(colors); // "red,green,blue"
389-
console.log(colors === options.colors); // true
414+
console.log(start.line); // 1
415+
console.log(start.column); // 1
416+
console.log(startIndex); // 0
390417
```
391418

392-
This modified example retrieves `options.colors` and stores it in the `colors` variable. Notice that `colors` is a direct reference to `options.colors` and not a copy.
393-
394-
Mixed destructuring is very useful for pulling values out of JSON configuration structures without navigating the entire structure.
419+
This code extracts `node.loc.start` and `node.loc.range[0]` into `start` and `startIndex`, respectively. Keep in mind that `loc:` and `range:` in the destructured pattern are just locations that correspond to properties in `node`. There is no part of `node` that cannot be extracted using destructuring when you use a mix of object and array destructuring. This approach is particularly useful for pulling values out of JSON configuration structures without navigating the entire structure.
395420

396421
## Destructured Parameters
397422

398-
In Chapter 1, you learned about destructuring assignment. Destructuring can also be used outside of the context of an assignment expression and perhaps the most interesting such case is with destructured parameters.
399-
400-
It's common for functions that take a large number of optional parameters to use an options object as one or more parameters. For example:
423+
There is another case where destructuring is useful, and that is as function arguments. A common pattern for JavaScript functions that take a large number of optional parameters is to use an options object whose properties specify the additional parameters. For example:
401424

402425
```js
426+
// properties on options represent additional parameters
403427
function setCookie(name, value, options) {
404428

405429
options = options || {};
@@ -412,15 +436,16 @@ function setCookie(name, value, options) {
412436
// ...
413437
}
414438

439+
// third argument maps to options
415440
setCookie("type", "js", {
416441
secure: true,
417442
expires: 60000
418443
});
419444
```
420445

421-
There are many `setCookie()` functions in JavaScript libraries that look similar to this. The `name` and `value` are required but everything else is not. And since there is no priority order for the other data, it makes sense to have an options object with named properties rather than extra named parameters. This approach is okay, although it makes the expected input for the function a bit opaque.
446+
There are many `setCookie()` functions in JavaScript libraries that look similar to this. The `name` and `value` are required but everything else is not. And since there is no priority order for the other data, it makes sense to have an options object with named properties rather than extra named parameters. This approach is okay, although it makes the expected input for the function a bit opaque because you cannot tell what is expected by looking at the function definition (you need to read the function body).
422447

423-
Using destructured parameters, the previous function can be rewritten as follows:
448+
Destructured parameters offer an alternative that makes it clearer as to what is expected. A destructured parameter uses an object or array destructuring pattern in place of a named parameter. The `setCookie()` function from the last example can be written using destructured parameters as:
424449

425450
```js
426451
function setCookie(name, value, { secure, path, domain, expires }) {
@@ -436,14 +461,18 @@ setCookie("type", "js", {
436461

437462
The behavior of this function is similar to the previous example, the biggest difference is the third argument uses destructuring to pull out the necessary data. Doing so makes it clear which parameters are really expected, and the destructured parameters also act like regular parameters in that they are set to `undefined` if they are not passed.
438463

439-
One quirk of this pattern is that the destructured parameters throw an error when the argument isn't provided. If `setCookie()` is called with just two arguments, it results in a runtime error:
464+
A>Destructured parameters have all of the capabilities of destructuring. You can use default values, mix object and array patterns, and use variable names that are different from the properties you're reading from. Everything you've learned so far in this chapter applies to destructured parameters as well.
465+
466+
### Destructured Parameters are Required
467+
468+
One quirk of using destructured parameters is that, by default, an error is thrown when they are not provided. For instance, the `setCookie()` function in the last example throws an error when only the first two arguments are passed, such as this:
440469

441470
```js
442471
// Error!
443472
setCookie("type", "js");
444473
```
445474

446-
This code throws an error because the third argument is missing (`undefined`). To understand why this is an error, it helps to understand that destructured parameters are really just a shorthand for destructured assignment. The JavaScript engine is actually doing this:
475+
This code throws an error because the third argument is missing (and so evaluates to `undefined`). To understand why this is an error, it helps to understand that destructured parameters are really just a shorthand for destructured declaration. The JavaScript engine is actually doing this:
447476

448477
```js
449478
function setCookie(name, value, options) {
@@ -454,9 +483,9 @@ function setCookie(name, value, options) {
454483
}
455484
```
456485

457-
Since destructuring assignment throws an error when the right side expression evaluates to `null` or `undefined`, the same is true when the third argument isn't passed.
486+
Since destructuring throws an error when the right side expression evaluates to `null` or `undefined`, the same is true when the third argument isn't passed to `setCookie()`.
458487

459-
You can work around this behavior by providing a default value for the destructured parameter:
488+
If you want the destructured parameter to be required, then this behavior isn't all that troubling. However, if you want the destructured parameter to be optional, you can work around this behavior by providing a default value for the destructured parameter:
460489

461490
```js
462491
function setCookie(name, value, { secure, path, domain, expires } = {}) {
@@ -465,13 +494,78 @@ function setCookie(name, value, { secure, path, domain, expires } = {}) {
465494
}
466495
```
467496

468-
This example now works exactly the same as the first example in this section. Providing the default value for the destructured parameter means that `secure`, `path`, `domain`, and `expires` will all be `undefined` if the third argument to `setCookie()` isn't provided.
497+
This example provides a default value of a new object for the third parameter. Providing the default value for the destructured parameter means that `secure`, `path`, `domain`, and `expires` will all be `undefined` if the third argument to `setCookie()` isn't provided (no error will be thrown).
498+
499+
### Default Values for Destructured Parameters
500+
501+
You can specify destructured default values for destructured parameters just as you would in destructured assignment. Just add the equals sign after the parameter and specify the default value. For example:
502+
503+
```js
504+
function setCookie(name, value,
505+
{
506+
secure = false,
507+
path = "/",
508+
domain = "example.com",
509+
expires = new Date(Date.now() + 360000000)
510+
}
511+
) {
512+
513+
// ...
514+
}
515+
```
516+
517+
Each property in the destructured parameter has a default value in this code, so you can avoid checking to see if a given property has been included in order to use the correct value. There are a couple of downsides to this approach. First, the function declaration gets quite a bit more complicated than usual. Second, If the destructured parameter is optional, then it still needs to be assigned a default value (an object), otherwise a call like `setCookie("type", "js")` still throws an error. That default value needs to have all the same default information as the destructured parameters. For example:
518+
519+
```js
520+
function setCookie(name, value,
521+
{
522+
secure = false,
523+
path = "/",
524+
domain = "example.com",
525+
expires = new Date(Date.now() + 360000000)
526+
} = {
527+
secure: false,
528+
path: "/",
529+
domain: "example.com",
530+
expires: new Date(Date.now() + 360000000)
531+
}
532+
) {
469533

470-
I> It's recommended to always provide the default value for destructured parameters to avoid these types of errors.
534+
// ...
535+
}
536+
```
471537

538+
Now the function declaration is even more complicated. The first object literal is the destructured parameter while the second is the default value. Unfortunately, this leads to a lot of repetition. You can eliminate some of the repetition by extracting the default values into a separate object and using that separate object as both part of the destructuring and the default parameter value:
539+
540+
```js
541+
var setCookieDefaults = {
542+
secure: false,
543+
path: "/",
544+
domain: "example.com",
545+
expires: new Date(Date.now() + 360000000)
546+
};
547+
548+
function setCookie(name, value,
549+
{
550+
secure = setCookieDefault.secure,
551+
path = setCookieDefault.path,
552+
domain = setCookieDefault.domain,
553+
expires = setCookieDefault.expires
554+
} = setCookieDefaults
555+
) {
556+
557+
// ...
558+
}
559+
```
560+
561+
In this code, the default values have been extracted into `setCookieDefaults`. The destructured parameter references that object directly for setting the default value of each binding and also as the overall default parameter value. If a default value needs to change, you can change it in `setCookieDefaults` and the data will end up being used in all of the correct spots. This is an unfortunate side effect of using destructured parameters, as handling all defaults can be complicated.
472562

473563
## Summary
474564

475-
Destructuring makes it easier to work with objects and arrays in JavaScript. Using syntax that's already familiar to many developers, object literals and array literals, you can now pick data structures apart to get at just the information you're interested in.
565+
Destructuring makes it easier to work with objects and arrays in JavaScript. Using syntax that's already familiar to many developers, object literals and array literals, you can now pick data structures apart to get at just the information you're interested in. Object patterns allow you to extract data from objects while array patterns let you extract data from arrays.
566+
567+
Both object and array destructuring can specify default values for any property or item that is `undefined` and both throw errors when the right side of an assignment evaluates to `null` or `undefined`. You can also navigate deeply nested data structures with object and array destructuring, descending to any arbitrary depth.
568+
569+
Destructuring declarations use `var`, `let`, or `const` to create variables and must always have an initializer. Destructuring assignments are used in place of other assignments and allow you to destructure into object properties and already-existing variables.
476570

477-
Destructured parameters use the destructuring syntax to make options objects more transparent when used as function parameters. The actual data you're interested in can be listed out along with other named parameters.
571+
Destructured parameters use the destructuring syntax to make options objects more transparent when used as function parameters. The actual data you're interested in can be listed out along with other named parameters. Destructured parameters can be array patterns, object patterns, or a mixture, and you can use all of the features of destructuring.

0 commit comments

Comments
 (0)