Object Destructuring

Object destructuring syntax uses an object literal on the left side of an assignment operation. For example:

  1. let node = {
  2. type: "Identifier",
  3. name: "foo"
  4. };
  5. let { type, name } = node;
  6. console.log(type); // "Identifier"
  7. console.log(name); // "foo"

In this code, the value of node.type is stored in a variable called type and the value of node.name is stored in a variable called name. This syntax is the same as the object literal property initializer shorthand introduced in Chapter 4. The identifiers type and name are both declarations of local variables and the properties to read the value from on the node object.

A> #### Don’t Forget the Initializer A> A>When using destructuring to declare variables using var, let, or const, you must supply an initializer (the value after the equals sign). The following lines of code will all throw syntax errors due to a missing initializer: A> A>js A>// syntax error! A>var { type, name }; A> A>// syntax error! A>let { type, name }; A> A>// syntax error! A>const { type, name }; A> A> A>While const always requires an initializer, even when using nondestructured variables, var and let only require initializers when using destructuring.

Destructuring Assignment

The object destructuring examples so far have used variable declarations. However, it’s also possible to use destructuring in assignments. For instance, you may decide to change the values of variables after they are defined, as follows:

  1. let node = {
  2. type: "Identifier",
  3. name: "foo"
  4. },
  5. type = "Literal",
  6. name = 5;
  7. // assign different values using destructuring
  8. ({ type, name } = node);
  9. console.log(type); // "Identifier"
  10. console.log(name); // "foo"

In this example, type and name are initialized with values when declared, and then two variables with the same names are initialized with different values. The next line uses destructuring assignment to change those values by reading from the node object. 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.

A destructuring assignment expression evaluates to the right side of the expression (after the =). That means you can use a destructuring assignment expression anywhere a value is expected. For instance, passing a value to a function:

  1. let node = {
  2. type: "Identifier",
  3. name: "foo"
  4. },
  5. type = "Literal",
  6. name = 5;
  7. function outputInfo(value) {
  8. console.log(value === node);
  9. }
  10. outputInfo({ type, name } = node); // true
  11. console.log(type); // "Identifier"
  12. console.log(name); // "foo"

The outputInfo() function is called with a destructuring assignment expression. The expression evaluates to node because that is the value of the right side of the expression. The assignment to type and name both behave as normal and node is passed into outputInfo().

W> An error is thrown when the right side of the destructuring 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.

Default Values

When you use a destructuring assignment statement, if you specify a local variable with a property name that doesn’t exist on the object, then that local variable is assigned a value of undefined. For example:

  1. let node = {
  2. type: "Identifier",
  3. name: "foo"
  4. };
  5. let { type, name, value } = node;
  6. console.log(type); // "Identifier"
  7. console.log(name); // "foo"
  8. console.log(value); // undefined

This code defines an additional local variable called value and attempts to assign it a value. However, there is no corresponding value property on the node object, so the variable is assigned the value of undefined as expected.

You can optionally define a default value to use when a specified property doesn’t exist. To do so, insert an equals sign (=) after the property name and specify the default value, like this:

  1. let node = {
  2. type: "Identifier",
  3. name: "foo"
  4. };
  5. let { type, name, value = true } = node;
  6. console.log(type); // "Identifier"
  7. console.log(name); // "foo"
  8. console.log(value); // true

In this example, the variable value is given true as a default value. The default value is only used if the property is missing on node or has a value of undefined. Since there is no node.value property, the variable value uses the default value. This works similarly to the default parameter values for functions, as discussed in Chapter 3.

Assigning to Different Local Variable Names

Up to this point, each example destructuring assignment has used the object property name as the local variable name; for example, the value of node.type was stored in a type variable. That works well when you want to use the same name, but what if you don’t? ECMAScript 6 has an extended syntax that allows you to assign to a local variable with a different name, and that syntax looks like the object literal nonshorthand property initializer syntax. Here’s an example:

  1. let node = {
  2. type: "Identifier",
  3. name: "foo"
  4. };
  5. let { type: localType, name: localName } = node;
  6. console.log(localType); // "Identifier"
  7. console.log(localName); // "foo"

This code uses destructuring assignment to declare the localType and localName variables, which contain the values from the node.type and node.name properties, respectively. The syntax type: localType says to read the property named type and store its value in the localType variable. This syntax is effectively the opposite of traditional object literal syntax, where the name is on the left of the colon and the value is on the right. In this case, the name is on the right of the colon and the location of the value to read is on the left.

You can add default values when using a different variable name, as well. The equals sign and default value are still placed after the local variable name. For example:

  1. let node = {
  2. type: "Identifier"
  3. };
  4. let { type: localType, name: localName = "bar" } = node;
  5. console.log(localType); // "Identifier"
  6. console.log(localName); // "bar"

Here, the localName variable has a default value of "bar". The variable is assigned its default value because there’s no node.name property.

So far, you’ve seen how to deal with destructuring of an object whose properties are primitive values. Object destructuring can also be used to retrieve values in nested object structures.

Nested Object Destructuring

By using a syntax similar to object literals, you can navigate into a nested object structure to retrieve just the information you want. Here’s an example:

  1. let node = {
  2. type: "Identifier",
  3. name: "foo",
  4. loc: {
  5. start: {
  6. line: 1,
  7. column: 1
  8. },
  9. end: {
  10. line: 1,
  11. column: 4
  12. }
  13. }
  14. };
  15. let { loc: { start }} = node;
  16. console.log(start.line); // 1
  17. console.log(start.column); // 1

The destructuring pattern in this example uses curly braces to indicate that the pattern should descend into the property named loc on node and look for the start property. Remember from the last section that whenever there’s a colon in a destructuring pattern, it means the identifier before the colon is giving a location to inspect, and the right side assigns a value. When there’s a curly brace after the colon, that indicates that the destination is nested another level into the object.

You can go one step further and use a different name for the local variable as well:

  1. let node = {
  2. type: "Identifier",
  3. name: "foo",
  4. loc: {
  5. start: {
  6. line: 1,
  7. column: 1
  8. },
  9. end: {
  10. line: 1,
  11. column: 4
  12. }
  13. }
  14. };
  15. // extract node.loc.start
  16. let { loc: { start: localStart }} = node;
  17. console.log(localStart.line); // 1
  18. console.log(localStart.column); // 1

In this version of the code, node.loc.start is stored in a new local variable called localStart. Destructuring patterns can be nested to an arbitrary level of depth, with all capabilities available at each level.

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

A> #### Syntax Gotcha A> A>Be careful when using nested destructuring because you can inadvertently create a statement that has no effect. Empty curly braces are legal in object destructuring, however, they don’t do anything. For example: A> A>js A>// no variables declared! A>let { loc: {} } = node; A> A> 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 gotcha to look out for.