What is destructuring assignment?
Destructuring assignment allows you to assign array and object property values to a series of variables using syntax similar to array or object literals. This syntax is very concise and clearer than traditional property access.
Access the first three items of an array without using destructuring assignment:
var first = someArray[0]; var second = someArray[1]; var third = someArray[2]; var first = someArray[0]; var second = someArray[1]; var third = someArray[2];
After using destructuring assignment, the corresponding code becomes more concise and readable:
var [first, second, third] = someArray; var [first, second, third] = someArray;
SpiderMonkey (Firefox’s JavaScript engine) already supports most of the features of destructuring assignment, but not completely.
Destructuring assignment of arrays and iterable objects
We have seen examples of array destructuring assignment above. The general form of this syntax is:
[ variable1, variable2, ..., variableN ] = array; [ variable1, variable2, ..., variableN ] = array;
This will assign the corresponding items in the array to variable1 to variableN in sequence. If you need to declare variables at the same time, you can add the var, let or const keywords in front of the destructuring expression.
var [ variable1, variable2, ..., variableN ] = array; let [ variable1, variable2, ..., variableN ] = array; const [ variable1, variable2, ..., variableN ] = array; var [ variable1, variable2, ..., variableN ] = array; let [ variable1, variable2, ..., variableN ] = array; const [ variable1, variable2, ..., variableN ] = array;
In fact, you can nest to any depth:
var [foo, [[bar], baz]] = [1, [[2], 3]]; console.log(foo); // 1 console.log(bar); // 2 console.log(baz); // 3 var [foo, [[bar], baz]] = [1, [[2], 3]]; console.log(foo); // 1 console.log(bar); // 2 console.log(baz); // 3
Also, it is possible to skip certain items in an array:
var [,,third] = ["foo", "bar", "baz"]; console.log(third); // "baz" var [,,third] = ["foo", "bar", "baz"]; console.log(third); // "baz"
You can also use a Rest expression to capture the remaining items in an array:
var [head, ...tail] = [1, 2, 3, 4]; console.log(tail); // [2, 3, 4] var [head, ...tail] = [1, 2, 3, 4]; console.log(tail); // [2, 3, 4]
If the array goes out of bounds or accesses an item that does not exist in the array, you will get the same value as accessing through the array index: undefined.
console.log([][0]); // undefined var [missing] = []; console.log(missing); // undefined console.log([][0]); // undefined var [missing] = []; console.log(missing); // undefined
Note that the method of array destructuring and assignment also applies to traversable objects:
function* fibs() { var a = 0; var b = 1; while (true) { yield a; [a, b] = [b, a + b]; } } var [first, second, third, fourth, fifth, sixth] = fibs(); console.log(sixth); // 5 function* fibs() { var a = 0; var b = 1; while (true) { yield a; [a, b] = [b, a + b]; } } var [first, second, third, fourth, fifth, sixth] = fibs(); console.log(sixth); // 5
Destructuring assignment of objects
Object destructuring assignment allows you to bind variables to different property values of the object. Specify the name of the property to be bound, followed by the variable to be bound:
var robotA = { name: "Bender" }; var robotB = { name: "Flexo" }; var { name: nameA } = robotA; var { name: nameB } = robotB; console.log(nameA); // "Bender" console.log(nameB); // "Flexo" var robotA = { name: "Bender" }; var robotB = { name: "Flexo" }; var { name: nameA } = robotA; var { name: nameB } = robotB; console.log(nameA); // "Bender" console.log(nameB); // "Flexo"
When the bound attribute name is the same as the variable name that receives the attribute value, there is another syntactic sugar:
var { foo, bar } = { foo: "lorem", bar: "ipsum" }; console.log(foo); // "lorem" console.log(bar); // "ipsum" var { foo, bar } = { foo: "lorem", bar: "ipsum" }; console.log(foo); // "lorem" console.log(bar); // "ipsum"
Like arrays, they can also be nested:
var complicatedObj = { arrayProp: [ "Zapp", { second: "Brannigan" } ] }; var { arrayProp: [first, { second }] } = complicatedObj; console.log(first); // "Zapp" console.log(second); // "Brannigan" var complicatedObj = { arrayProp: [ "Zapp", { second: "Brannigan" } ] }; var { arrayProp: [first, { second }] } = complicatedObj; console.log(first); // "Zapp" console.log(second); // "Brannigan"
When destructuring a property that does not exist, you will get undefined:
var { missing } = {}; console.log(missing); // undefined var { missing } = {}; console.log(missing); // undefined
There is another potential pitfall when using destructuring assignment of objects, there is no variable declaration (no var, let or const keyword) during destructuring assignment:
{ blowUp } = { blowUp: 10 }; // Syntax error { blowUp } = { blowUp: 10 }; // Syntax error
This is because JavaScript syntax tells the engine that any statement starting with { is a statement block (for example, {console} is a legal statement block). The solution is to wrap the entire statement with a pair of parentheses:
({ safe } = {}); // No errors ({ safe } = {}); // No errors
Other situations
When you try to destructure null or undefined, you will get a type error:
var {blowUp} = null; // TypeError: null has no properties var {blowUp} = null; // TypeError: null has no properties
However, you can destructure other basic types (Boolean, String and Number) and you will get undefined:
var {wtf} = NaN; console.log(wtf); // undefined var {wtf} = NaN; console.log(wtf); // undefined
The results may surprise you, but if you look deeper, the reason is actually very simple. When performing object destructuring and assignment, the destructured object will be coerced into Object. Except for null and undefined, other types can be coerced into objects. When performing structure assignment of an array, the destructured object is required to have a traverser.
Default value
You can specify a default value for non-existing properties:
var [missing = true] = []; console.log(missing); // true var { message: msg = "Something went wrong" } = {}; console.log(msg); // "Something went wrong" var { x = 3 } = {}; console.log(x); // 3 var [missing = true] = []; console.log(missing); // true var { message: msg = "Something went wrong" } = {}; console.log(msg); // "Something went wrong" var { x = 3 } = {}; console.log(x); // 3
Practical Application
Function parameters
As developers, we often use an object containing multiple properties as a function parameter to implement a more flexible API, rather than asking API users to remember some parameters in a specific order. We can use destructuring assignment of objects to avoid attribute access every time a parameter is used:
function removeBreakpoint({ url, line, column }) { // ... } function removeBreakpoint({ url, line, column }) { // ... }
Configuration object
To improve the above example, we can provide default values for the object properties to be destructured. This is very practical for objects that are used as configuration parameters, because many configuration items have a reasonable default value. For example, the second parameter of jQuery's ajax method is a configuration object, which we can implement like this:
jQuery.ajax = function (url, { async = true, beforeSend = noop, cache = true, complete = noop, crossDomain = false, global = true, // ... more config }) { // ... do stuff }; jQuery.ajax = function (url, { async = true, beforeSend = noop, cache = true, complete = noop, crossDomain = false, global = true, // ... more config }) { // ... do stuff };
This avoids duplication of code like this: var foo = config.foo || theDefaultFoo;.
Used with iterators
When traversing a Map object, we can use destructuring assignment to traverse [key, value]:
var map = new Map(); map.set(window, "the global"); map.set(document, "the document"); for (var [key, value] of map) { console.log(key + " is " + value); } // "[object Window] is the global" // "[object HTMLDocument] is the document" var map = new Map(); map.set(window, "the global"); map.set(document, "the document"); for (var [key, value] of map) { console.log(key + " is " + value); } // "[object Window] is the global" // "[object HTMLDocument] is the document"
Traverse keys only:
for (var [key] of map) { // ... } for (var [key] of map) { // ... } 只遍历值: for (var [,value] of map) { // ... } for (var [,value] of map) { // ... }
Return multiple values
Returns an array, and extracts the return value through destructuring assignment:
function returnMultipleValues() { return [1, 2]; } var [foo, bar] = returnMultipleValues(); function returnMultipleValues() { return [1, 2]; } var [foo, bar] = returnMultipleValues();
Or, return an object of key-value pairs:
function returnMultipleValues() { return { foo: 1, bar: 2 }; } var { foo, bar } = returnMultipleValues(); function returnMultipleValues() { return { foo: 1, bar: 2 }; } var { foo, bar } = returnMultipleValues();
Both of these are better than using intermediate variables:
function returnMultipleValues() { return { foo: 1, bar: 2 }; } var temp = returnMultipleValues(); var foo = temp.foo; var bar = temp.bar; function returnMultipleValues() { return { foo: 1, bar: 2 }; } var temp = returnMultipleValues(); var foo = temp.foo; var bar = temp.bar;
Use continuation form:
function returnMultipleValues(k) { k(1, 2); } returnMultipleValues((foo, bar) => ...); function returnMultipleValues(k) { k(1, 2); } returnMultipleValues((foo, bar) => ...);
导入 CommonJS 模块的指定部分
还没使用过 ES6 的模块吧,那至少使用过 CommonJS 吧。当导入一个 CommonJS 模块 X 时,模块提供的方法也许多余你实际使用的。使用解构赋值,你可以明确指定你需要使用模块的哪些部分:
const { SourceMapConsumer, SourceNode } = require("source-map"); const { SourceMapConsumer, SourceNode } = require("source-map");
如果你使用 ES6 的模块机制,你可以看到 import 声明时有一个类似的语法。
结论
我们看到,解构赋值在很多场景下都很实用。在 Mozilla,我们已经有很多经验。Lars Hansen 在 10 年前就向 Opera 引入了解构赋值,Brendan Eich 在稍微晚点也给 Firefox 添加了支持,最早出现在 Firefox 2 中。因此,解构赋值已经渗透到我们每天对 JS 的使用中,悄悄地使我们的代码更简短、整洁。