DEV Community

Cover image for You may not know destructuring yet
Mahesh Pratap
Mahesh Pratap

Posted on

You may not know destructuring yet

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
// 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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

[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 
Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Source
[1]: You Don't Know JS: ES6 & Beyond By Kyle Simpson

Top comments (4)

Collapse
 
aminnairi profile image
Amin • Edited

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.

const myInput = document.getElementById("myInput");

if (null !== myInput) {
    myInput.addEventListener("input", event => {
        console.log(`input value: ${event.target.value}.`);
        console.log(`input value uppercased: ${event.target.value.toUpperCase()}.`);
        console.log(`input value lowercased: ${event.target.value.toLowerCase()}.`);
    });
}
Enter fullscreen mode Exit fullscreen mode

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.

const myInput = document.getElementById("myInput");

if (null !== myInput) {
    myInput.addEventListener("input", ({target}) => {
        console.log(`input value: ${target.value}.`);
        console.log(`input value uppercased: ${target.value.toUpperCase()}.`);
        console.log(`input value lowercased: ${target.value.toLowerCase()}.`);
    });
}
Enter fullscreen mode Exit fullscreen mode

We can even go further by nesting the de-structuring.

const myInput = document.getElementById("myInput");

if (null !== myInput) {
    myInput.addEventListener("input", ({target: {value}}) => {
        console.log(`input value: ${value}.`);
        console.log(`input value uppercased: ${value.toUpperCase()}.`);
        console.log(`input value lowercased: ${value.toLowerCase()}.`);
    });
}
Enter fullscreen mode Exit fullscreen mode

Pretty cool to shorten the syntax and remove some repetitions in the code. Though it would be interesting to provide a real-world example.

Collapse
 
xavierbrinonecs profile image
Xavier Brinon

myInput?.addEventListener allows to get read of the if statement.
Always good to keep your code as close to the left as possible.

Collapse
 
shrihankp profile image
Shrihan

No you are wrong. All browsers support ES10 as well

Using Babel ✌️😀

Nice article 👍

Collapse
 
bias profile image
Tobias Nickel • Edited

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.