It's been a while since ES6 introduced the destructuring assignment, which is now supported in all major browsers. It can be used in any valid assignment operation i.e., variable assignment, function parameter assignment, etc. Let's get a basic overview of what destructing is first.
The syntax before the introduction of destructuring was too verbose for assigning values of an array or object to individual variables.
const array = [1, 2, 3];
const obj = { x: 4, y: 5, z: 6 };
const a = array[0], b = array[1], c = array[2];
const x = obj.x, y = obj.y, z = obj.z
console.log(a, b, c); // 1 2 3
console.log(x, y, z); // 4 5 6
That's how we used to do in the pre-destructuring era. Now let's do it the ES6 way.
const array = [1, 2, 3];
const obj = { x: 4, y: 5, z: 6 };
const [ a, b, c ] = array;
const { x, y, z } = obj;
console.log(a, b, c); // 1 2 3
console.log(x, y, z); // 4 5 6
Way better. Having a basic idea, let's dig deeper.
Object property assignment pattern
Let's dig into that const { x, y, z } = obj
syntax from the previous snippet. It's actually shorthand for const { x: x, y: y, z: z } = obj
. If the property name being matched is the same as the variable you want to declare you can shorten the syntax. Here we leave x:
part of { x: x, .. }
.
Usually, assignment operations follow target=source
or target: source
pattern but in the destructuring assignment, source: target
pattern is followed. Consider:
const obj = { x: 4, y: 5, z: 6 };
const { x: X, y: Y, z: Z } = obj;
console.log(X, Y, Z); // 4, 5, 6
Here variables named X, Y, Z
are declared and the values of obj.x, obj.y, obj.z
is being assigned to them respectively. Hence, if the property name being matched is the same as the variable, you can use the shorthand. JS engine will do our work for us while we keep our code clean.
Assign later
As mentioned earlier destructuring can be used in any valid assignment operation. This means you don't always need to use destructuring operation while declaring variables. For already declared variable destructuring only does assignments, exactly as we have seen with other variable assignments. Consider:
let a, b, c, x, y, z;
[a, b, c] = [1, 2, 3];
({x, y, z} = { x: 4, y: 5, z: 6 });
console.log(a, b, c, x, y, z); // 1 2 3 4 5 6
We need to wrap the whole assignment expression in ()
for object destructuring, otherwise { .. }
on the LHS will be treated as a block statement instead of an object.
The assignment expressions don't need to be just variable identifiers. Anything that's a valid assignment expression is allowed. For example:
// Object assignment
const obj = {};
const computedProp = 'z';
[obj.a, obj.b, obj.c] = [1, 2, 3];
({ x: obj.x, y: obj.y, [computedProp]: obj[computedProp] } = { x: 4, y: 5, z: 6 });
console.log(obj.a, obj.b, obj.c); // 1 2 3
console.log(obj.x, obj.y, obj.z); // 4 5 6
// Array assignment
let array = [];
({ x: array[0], y: array[1], z: array[2] } = { x: 4, y: 5, z: 6 });
console.log(array); // [4, 5, 6]
Remember that traditional "swap two variables without temp variable" question, now we have a new solution for that:
let x = 10, y = 20;
[y, x] = [x, y];
console.log(x, y); // 20 10
Repeated Assignments
The object destructuring form allows a source property to be listed multiple times. For example:
const { a: X, a: Y } = { a: 1 };
console.log(X, Y); // 1 1
This is not possible in array destructuring, obviously 😒️.
Chaining
The return value of any destructuring assignment expression is the source object/array. Let's see what that means:
let a, b, c, x, y, z;
console.log({x, y, z } = { x: 4, y: 5, z: 6 }); // { x: 4, y: 5, z: 6 }
console.log([a, b, c] = [1, 2, 3]); // [1, 2, 3]
By carrying the object value through as the completion, you can chain destructuring assignment expressions together:
let a, b, c, x, y, z;
[a, b] = [c] = [1, 2, 3];
( {x} = {y,z} = {x: 4, y: 5, z: 6} );
console.log(a, b, c); // 1 2 1
console.log(x, y, z); // 4 5 6
[a, b] = [c] = [1, 2, 3]
& ( {x} = {y,z} = {x: 4, y: 5, z: 6} )
is evaluated right to left (i.e., return value of [c] = [1, 2, 3]
is assigned to [a, b]
similarly, the return value of {y,z} = {x: 4, y: 5, z: 6}
is assigned to {x}
.
Optional Assignment
You don't have to assign all the values that are present in the source object/array in the destructuring assignment. For example:
const [,,c] = [1, 2, 3];
const { y } = { x: 4, y: 5, z: 6 };
console.log(c, y); // 3 5
You can skip the values that are not required in the current scope.
Gathering
You can use the rest syntax in a destructuring assignment to gather values in a single variable. For example:
const [a, ...b] = [1, 2, 3];
const { x, ...y } = { x: 4, y: 5, z: 6 };
console.log(a, b); // 1 [2, 3]
console.log(x, y); // 4 { y: 5, z: 6 }
It behaves similarly to the Functions Rest Parameters.
Default Value Assignment
You can provide a default value for an assignment in both object and array destructuring assignments. For example:
const { w = 7, x = 8, y, z } = { x: 4, y: 5, z: 6 };
const [ a = 9, b, c, d = 10 ] = [1, 2, 3];
console.log(w, x, y, z); // 7 4 5 6
console.log(a, b, c, d); // 1 2 3 10
Default values will be applied for missing values in the source array/object. It is similar to the Default function parameters.
You can combine the default variable assignment with alternative assignment expression syntax. For example:
const { w: WW = 10, x, y, z } = { x: 4, y: 5, z: 6 };
console.log(WW, x, y, z); // 10 4 5 6
Nested Destructuring
If the values you're destructuring have nested objects or arrays, you can destructure those values too:
const array = [ 1, [2, 3, 4], 5 ];
const obj = {
x: {
y: {
z: 6
}
}
}
const [ a, [ b, c, d ], e ] = array;
const {
x: {
y: {
z: w
}
}
} = obj;
console.log(a, b, c, d, e); // 1 2 3 4 5
consoel.log(x); // {y: {z: 6}}
console.log(y) // {z: 6}
console.log(w); // 6
Destructuring Parameters
All the above rules apply while destructuring function parameters too, as behind the scene the arguments are being assigned to parameters. For example:
function baz([x, y]) {
console.log(x, y);
}
baz([1, 2]); // 1 2
baz([1]); // 1 undefined
baz([, 2]); // undefined 2
Source
[1]: You Don't Know JS: ES6 & Beyond By Kyle Simpson
Top comments (4)
Hi Mahesh and thanks for your interesting article.
We can also use the de-structuring when dealing with event listener's callback variables for instance.
Here, I'm using three times the value from my input. We can shorten it by using the de-structuring in the callback variable directly.
We can even go further by nesting the de-structuring.
Pretty cool to shorten the syntax and remove some repetitions in the code. Though it would be interesting to provide a real-world example.
myInput?.addEventListener allows to get read of the if statement.
Always good to keep your code as close to the left as possible.
No you are wrong. All browsers support ES10 as well
Using Babel ✌️😀
Nice article 👍
nested destructuring is always the hardest for me.
Besides all the nice syntax, I like very much, that it makes people use more consistent naming across modules.