ES6 Destructuring In-Depth
Can Ho provides us with an in-depth look at extracting data from data sources in JavaScript ES6. Read on and see how it's done.
Join the DZone community and get the full member experience.
Join For FreeSyntax
Destructuring is a convenient way in ES6 for extracting data from data sources. Data sources can be objects and array-like objects. Destructuring has the following form:
«pattern» = «data source»
- «data source»: also known as destructuring source, is the object to be destructured.
- «pattern»: also known as destructuring target, is the pattern for extracting data from the data source.
The «data source» can be any objects except null or undefined. Behind the scenes, the JS engine will coerce the «data source» using internal operation ToObject before destructuring accesses the «data source» properties.
«pattern» = ToObject(«data source»)
When arguments are null and undefined, ToObject will throw a TypeError exception. So, when using null and undefined as «data source»s, destructuring an operation will fail before accessing properties.
The pattern can be:
- Object pattern: { prop1: «pattern», prop2: «pattern», prop3: «pattern», …}
- Array pattern: [«pattern», «pattern», «pattern»,…,«pattern»]
var { x: x } = null; //TypeError: Cannot match against 'undefined' or 'null'
var { y: y } = undefined; //TypeError: Cannot match against 'undefined' or 'null'
var o = {name: 'John', age: 25, title: 'Software developer'};
//ES5
var name = o.name;
var age = o.age;
var title = o.title;
console.log(name, age, title); //John 25 Software developer
//ES6
var {name: name, age: age, title: title} = o;
console.log(name, age, title); //John 25 Software developer
var arr = [10, 20, 30];
//ES5
var x = arr[0];
var y = arr[1];
var z = arr[2];
console.log(x, y, z); //10 20 30
//ES6
var [x, y, z] = arr;
console.log(x, y, z); //10 20 30
With ES6 destructuring, our code is shorter and looks much nicer. When the name of the property being extracted is the same as the variable we want declare to, we can shorten the syntax:
var {prop1, prop2, prop3} = o;
//equivalent to
var {prop1: prop1, prop2: prop2, prop3: prop3} = o;
We can rewrite above examples like this:
var o = {name: 'John', age: 25, title: 'Software developer'};
var {name, age, title} = o;
Constructing Data vs Destructuring Data
The quick way to create an object in JS is to use the object literal form:
var o = {
name: 'John',
age: 25,
title: 'Software developer'
};
In object literal form, we know that name is the object property and the string ‘John’ is the source data that is assigned to name. The pattern here is «target» : «source»
. We can easily understand this because this is similar to = assignment («target» = «source»
)
With ES6 destructuring, we revert the pattern «target» : «source»
:
var o = {
name: 'John',
age: 25,
title: 'Software developer'
};
var {name: n, age: a, title: t} = o;
console.log(name, age, title); //ReferenceError
console.log(n, a, t); //John 25 Software developer
The pattern for destructuring is «source» : «target»
. In other words, in constructing an object with object literal form, we use «target» : «source»
while in destructuring an object we use «source» : «target»
. See the difference?
Usage
Variable Declaration
We can use destructuring assignment with var, let, const
like in previous examples.
let o = {
name: 'John',
age: 25,
title: 'Software developer'
};
let {name: n, age: a, title: t} = o;
console.log(n, a, t); //John 25 Software developer
let numbers = [1, 2, 3];
let [x, y, z] = numbers;
console.log(x, y, z); // 1 2 3
Of course, we can use shorter syntax:
let o = {
name: 'John',
age: 25,
title: 'Software developer'
};
let {name, age, title} = o;
console.log(name, age, title); //John 25 Software developer
Assignment
We can use destructuring with assignment as well.
let o = {
name: 'John',
age: 25,
title: 'Software developer'
};
let n, a, t;
({name: n, age: a, title: t} = o);
console.log(n, a, t); //John 25 Software developer
let numbers = [1, 2, 3];
let x, y, z;
[x, y, z] = numbers;
console.log(x, y, z); // 1 2 3
Note that in the first example with object destructuring, when we don’t use var
, let
, or const
but instead we have to wrap the whole assignment in (..)
because {..}
alone will create a block statement instead of an object.
Actually, the «target»s (e.g n, x, y..) in destructuring don’t need to be just variables. They can be anything that creates valid assignments:
let o = {
name: 'John',
age: 25,
title: 'Software developer'
};
let o1 = {};
({name: o1.n, age: o1.a, title: o1.t} = o);
console.log(o1); //{n: "John", a: 25, t: "Software developer"}
let numbers = [1, 2, 3];
let nums = [];
[nums[2], nums[1], nums[0]] = numbers;
console.log(nums); // 3 2 1
let o2 = {a: 1, b: 2, c: 3};
let arr = [];
({a: arr[0], b: arr[1], c: arr[2]} = o2);
Note that the source property can be listed many times:
let o = {x: 1, y: 2, z: 3};
let {x : a, x: b, y: c} = o;
Destructuring also works with computed property names.
let o = {x: 1, y: 2, z: 3, t: 4};
let o1 = {};
let i = 0;
let prop = "x";
({[prop]: o1[prop + i++], y: o1[prop + i++], z: o1[prop + i++], t: o1[prop + i++]} = o);
console.log(o1); // {x0: 1, x1: 2, x2: 3, x3: 4}
let arr = [1, 2, 3, 4];
let o2 = {};
i = 0;
[o2[prop + i++], o2[prop + i++], o2[prop + i++], o2[prop + i++]] = arr;
console.log(o2); //{p0: 1, p1: 2, p2: 3, p3: 4}
As you can see, we can use computed property names in both «target» ([prop + i++] and «source» ([prop]).
Pick What You Need
With object destructuring and array destructuring, we don’t have to extract all properties/elements. We just need to pick what we need.
let o = {
name: 'John',
age: 25,
title: 'Software developer'
};
let {name, age} = o; // name='John', age=25
Array destructuring supports a syntax of Array holes to skip elements during destructuring.
var x, y, z;
let arr = [1, 2, 3, 4, 5];
[x, y] = arr; // x=1, y=2
[x, , y, , z] = arr; // x=1, y=3, z=5
[, , , , z] = arr; // z=5
Rest Operator (…)
Rest operator (…) collects the remaining elements/properties of an array/object into an array or an object.
let arr = [1, 2, 3, 4, 5];
let [x, y, ...z] = arr; // x=1, y=2, z=[3, 4, 5]
let o = {
name: 'John',
age: 25,
title: 'Software developer'
};
let {name, ...otherProps} = o;// name='John', otherProps = {age: 25, title: 'Software Developer'}
Note:
- Rest operator must be the last part of an array/object
- Rest operator actually doesn’t work for object. However, if you’re using a transpiler like Babel, you’re covered.
As rest operator collects the remaining elements/properties of an array/object, we can use a pattern too.
let arr = [1, 2, 3, 4, 5];
let [x, y, ...[z, t, e]] = arr; // x=1, y=2, z=3, t=4, e = 5
If there is no matching for the rest operator, an empty array is matched against (not null or undefined):
let arr = [1];
let [x, y, ...[z]] = arr; // x=1, y=undefined, z= []
Default Values
Both object destructuring and array destructuring support default values for:
- Variables
- Patterns
Note: Missing and undefined will trigger default values.
Default Values for Variables
If a part in the destructuring target has no match in the destructuring source, it matches against its default value (if specified). Otherwise, it matches against undefined.
let [x, y = 3] = []; //x=undefined, y=3
let [x = 2, y = 3] = []; // x=2, y=3
let [x = 2, y = 3] = [undefined]; // x=2, y=3
let [x = 2, y = 3] = [1]; // x=1, y=3
let {x: x1, y: y1=3} = {}; //x1=undefined, y1=3
let {x: x1=1, y: y1=3} = {x: undefined}; //x1=1, y1=3
let {x: x1=1, y: y1=3} = {}; //x1=1, y1=3
let {x: x1=1, y: y1=3} = {x: 10}; //x1=10, y1=3
let {x: x1=1, y: y1=3} = {x: 10, y: 20}; //x1=10, y1=20
Default values can refer to other variables on the left as well:
let [x = 2, y = 3, z = y] = [1]; // x=1, y=3, z=3
let {x: x1=1, y: y1=3, z: z1=x1} = {x: 10}; //x1=10, y1=3, z1: 10
For object destructuring, we can use object shorthand syntax:
let {x=20, y=10, z=x} = {x: 10}; //x=10, y=10, z=10
Default Values for Patterns
We can use default values for patterns. This is a less known feature.
{prop1: «pattern»=val1, prop2: «pattern»=val2,...} = «source»
let {x: {y: z} = {y: 10}} = {}; // z=10
let {x: {y: z} = {y: 10}} = {x: undefined}; // z=10
let {x: {y: z} = {y: 10}} = {y: {y: 100}}; // z=10
let {x: {y: z} = {y: 10}} = {x: {y: 100}}; // z=100
In the first 2 examples, as there is no property x in the source object, the pattern {y: z} matches against default value {y: 10}
[«pattern»=val1, «pattern»=val2,...] = «source»
let [{x: y}={x: 3}] = []; // y=3
let [{x: y}={x: 3}] = [{}]; // y=undefined
let [{x: y}={x: 3}] = [{x: 10}]
Combining Both of Them
We can combine both of variable default values and pattern default values.
[{ prop: x=100} = {prop: 10}] = [{prop: 99}]; //x=99
[{ prop: x=100} = {prop: 10}] = []; //x=10
[{ prop: x=100} = {prop: 10}] = [undefined]; //x=10
[{ prop: x=100} = {prop: undefined}] = [undefined]; //x=100
[{ prop: x=100} = {}] = [undefined]; //x=100
[{ prop: x=100 } = {prop: 10}] = [{}]; //x=100
[{ prop: x=100 } = {prop: 10}] = [{prop1: 0}]; //x=100
Destructuring Parameters
Let’s take a look array destructuring for parameters
function test([x, y]) {
console.log(x, y);
}
test([]);// undefined undefined
test[1]);// 1 undefined
test([1, 2]);// 1 2
What if we don’t pass any parameters or we pass non-iterable parameters like this test()
or test(1)
? A TypeError is thrown TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined
. If you’re still in doubt, refer to syntax section.
We can improve this a bit by using default values for patterns:
function test([x, y]=[]) {
console.log(x, y);
}
test();// undefined undefined
What if we want x and y to be always initialized with default values as well. Just use default values for variables:
function test([x=0, y=0]=[]) {
console.log(x, y);
}
test();// 0 0
test([1]);// 1 0
test([1, 2]);// 1 2
Now, let’s take a look at object destructuring for parameters:
function test({x, y}) {
console.log(x, y);
}
test();// TypeError: Cannot match against 'undefined' or 'null'
test({}};// undefined undefined
test(1);// undefined undefined
test({x: 1});// 1 undefined
test({x:1, y:2}); // 1 2
To prevent TypeError
when no parameters are passed, we can use default values for patterns:
function test({x, y}={}) {
console.log(x, y);
}
test();// undefined undefined
We can combine default values for variables and default values for patterns:
function test({x=2, y=2}={}) {
console.log(x, y);
}
test();// 2 2
test(undefined);// 2 2
test(1);// 2 2
test({});// 2 2
test({x: 3});// 3 2
test({x:3, y: 3});// 3 3
Not Just Arrays
For array destructuring, the source object can be anything that is iterable, not just arrays:
- Built-in Iterables:
- String
- Array
- TypedArray
- Map
- Set
- Custom Iterables:
let [c1, c2, c3,,c4] = "hello";
console.log(c1, c2, c3, c4);
const map = new Map().set('k1', 'val1').set('k2', 'val2');
let [e1, e2] = map;
console.log(e1, e2);//["k1", "val1"] ["k2", "val2"]
let [[k1, v1], [k2, v2]] = map;//k1="k1", v1="val1", k2="k2", v2="val2"
for([key, val] of map) {
console.log(`${key} -->${val}`);
}
//k1 -->val1
//k2 -->val2
Let’s define a custom iterable. To make an object iterable, we need to add [Symbol.iterator]
method. [Symbol.iterator]
method must return an iterator object.
let myIterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
For simplicity, we just borrow the [Symbol.iterator]
method from Array.
[x, y, z] = myIterable;//x="a", y="b", z="c"
Published at DZone with permission of Can Ho, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
DZone's Article Submission Guidelines
-
Avoiding Pitfalls With Java Optional: Common Mistakes and How To Fix Them [Video]
-
Comparing Cloud Hosting vs. Self Hosting
-
Observability Architecture: Financial Payments Introduction
Comments