Skip to content

Instantly share code, notes, and snippets.

@mikaelbr
Last active February 21, 2024 20:41
Show Gist options
  • Save mikaelbr/9900818 to your computer and use it in GitHub Desktop.
Save mikaelbr/9900818 to your computer and use it in GitHub Desktop.
Complete collection of JavaScript destructuring. Runnable demos and slides about the same topic: http://git.mikaelb.net/presentations/bartjs/destructuring
// === Arrays
var [a, b] = [1, 2];
console.log(a, b);
//=> 1 2
// Use from functions, only select from pattern
var foo = () => [1, 2, 3];
var [a, b] = foo();
console.log(a, b);
// => 1 2
// Omit certain values
var [a, , b] = [1, 2, 3];
console.log(a, b);
// => 1 3
// Combine with spread/rest operator (accumulates the rest of the values)
var [a, ...b] = [1, 2, 3];
console.log(a, b);
// => 1 [ 2, 3 ]
// Fail-safe.
var [, , , a, b] = [1, 2, 3];
console.log(a, b);
// => undefined undefined
// Swap variables easily without temp
var a = 1, b = 2;
[b, a] = [a, b];
console.log(a, b);
// => 2 1
// Advance deep arrays
var [a, [b, [c, d]]] = [1, [2, [[[3, 4], 5], 6]]];
console.log("a:", a, "b:", b, "c:", c, "d:", d);
// => a: 1 b: 2 c: [ [ 3, 4 ], 5 ] d: 6
// === Objects
var {user: x} = {user: 5};
console.log(x);
// => 5
// Fail-safe
var {user: x} = {user2: 5};
console.log(x);
// => undefined
// More values
var {prop: x, prop2: y} = {prop: 5, prop2: 10};
console.log(x, y);
// => 5 10
// Short-hand syntax
var { prop, prop2} = {prop: 5, prop2: 10};
console.log(prop, prop2);
// => 5 10
// Equal to:
var { prop: prop, prop2: prop2} = {prop: 5, prop2: 10};
console.log(prop, prop2);
// => 5 10
// === Potential grammar hiccups
// Oops: This doesn't work:
var a, b;
{ a, b } = {a: 1, b: 2};
// But this does work
var a, b;
({ a, b } = {a: 1, b: 2});
console.log(a, b);
// => 1 2
// This due to the grammar in JS.
// Starting with { implies a block scope, not an object literal.
// () converts to an expression.
// From Harmony Wiki:
// Note that object literals cannot appear in
// statement positions, so a plain object
// destructuring assignment statement
// { x } = y must be parenthesized either
// as ({ x } = y) or ({ x }) = y.
// === Combined destructuring of objects and arrays
// Combine objects and arrays
var {prop: x, prop2: [, y]} = {prop: 5, prop2: [10, 100]};
console.log(x, y);
// => 5 100
// === Nested object destructuring
// Deep objects
var {
prop: x,
prop2: {
prop2: {
nested: [ , , b]
}
}
} = { prop: "Hello", prop2: { prop2: { nested: ["a", "b", "c"]}}};
console.log(x, b);
// => Hello c
// === Combining all to make fun happen
// All well and good, can we do more? Yes!
// Using as method parameters
var foo = function ({prop: x}) {
console.log(x);
};
foo({invalid: 1});
foo({prop: 1});
// => undefined
// => 1
// === Nested advanced examples
// Can also use with the advanced example
var foo = function ({
prop: x,
prop2: {
prop2: {
nested: b
}
}
}) {
console.log(x, ...b);
};
foo({ prop: "Hello", prop2: { prop2: { nested: ["a", "b", "c"]}}});
// => Hello a b c
// === In combination with other ES2015 features.
// Computed property names
const name = 'fieldName';
const computedObject = { [name]: name }; // (where object is { 'fieldName': 'fieldName' })
const { [name]: nameValue } = computedObject;
console.log(nameValue)
// => fieldName
// === Rest and defaults
var ajax = function ({ url = "localhost", port: p = 80}, ...data) {
console.log("Url:", url, "Port:", p, "Rest:", data);
};
ajax({ url: "someHost" }, "additional", "data", "hello");
// => Url: someHost Port: 80 Rest: [ 'additional', 'data', 'hello' ]
ajax({ }, "additional", "data", "hello");
// => Url: localhost Port: 80 Rest: [ 'additional', 'data', 'hello' ]
ajax({ });
// => Url: localhost Port: 80 Rest: []
// Doesn't work due to trying to destructure undefined
ajax();
// => Uncaught TypeError: Cannot match against 'undefined' or 'null'
// To fix this we need to have default value for parameter in function
// Note: See the `= {}` at the end, saying default empty object if the first argument is undefined.
var ajax = ({ url: url = "localhost", port: p = 80} = {}) => {
console.log("Url:", url, "Port:", p);
};
// Now this works.
ajax();
// => Url: localhost Port: 80
ajax({ });
// => Url: localhost Port: 80
ajax({ port: 8080 });
// => Url: localhost Port: 8080
ajax({ url: "someHost", port: 8080 });
// => Url: someHost Port: 8080
// === Similar to _.pluck
var users = [
{ user: "Name1" },
{ user: "Name2" },
{ user: "Name2" },
{ user: "Name3" }
];
var names = users.map( ({ user }) => user );
console.log(names);
// => [ 'Name1', 'Name2', 'Name2', 'Name3' ]
// === Usage in for..of loops
var users = [
{ user: "Name1" },
{ user: "Name2", age: 2 },
{ user: "Name2" },
{ user: "Name3", age: 4 }
];
for (let { user, age = "DEFAULT AGE" } of users) {
console.log(user, age);
}
// => Name1 DEFAULT AGE
// => Name2 2
// => Name2 DEFAULT AGE
// => Name3 4
@gg-spyda
Copy link

Thank you

@gitasong
Copy link

How would you use array destructuring in a for-of loop? I keep seeing syntax like this, but don't understand it:

it('in for-of loop', () => {
    for (var [a, b] of [[0, 1, 2]]) {}
    expect([a, b]).toEqual([1, 2])
  })

(Ignore the test suite syntax. How do I rewrite the for line to get for (var [a, b] of [[0, 1, 2]]) to equal [1, 2]?)

@mikaelbr
Copy link
Author

mikaelbr commented Feb 5, 2018

@gitasong There are a couple of non-destructuring grammar rules in play in your example, I'm just going to explain some of them to avoid potential misunderstandings for other potential readers.

When we iterate and use var to declare, it's hoisted to the top of the scope and the value of the last item in the list we iterate over is accessible. So if we did:

for (var a of [1, 2, 3]) {}
console.log(a); // Output: 3

As this is essentially the same as:

var a;
a = 1;
a = 2;
a = 3;
console.log(a); // Output: 3

This would not be the case if we used let or const:

for (let z of [1, 2, 3]) {}
console.log(z); // z is not defined

This explains why we can use a and b in your example even though it's outside the body of the for-of loop. Looking at the assignment/destructuring part, there are 2 parts to this: the destructuring in assignment part of the loop, and the destructuring of values it self.

for (var [a, b] of [[0, 1, 2]])

Says for each iteration, take the current item (in this case it's only one item, [0, 1, 2]) and destructure to a and b. If the list had additional values, it would be re-assigned accordingly. If we were to write the iteration as 1 by 1 imperative statements we would write:

// instead of something like
for (var [a, b] of [[0, 1, 2], [3, 4, 5]]) console.log(a, b);

// it would be
var a, b;
[a, b] = [0, 1, 2];
console.log(a, b); // 0 1

[a, b] = [3, 4, 5];
console.log(a, b); // 3 4

From this, we can also see that the destructuring part of it is a normal array destructuring. And seeing the gist above, we can omit values in destructuring by not having an identifier.

Meaning, we can ignore the first value in the array (the 0) and only select 1 and 2:

var a, b;
[   , a, b] = [0, 1, 2];
// ^--- note the comma. Ignoring the first item

console.log(a, b); // Output: 1 2

This was a very long winded and maybe too detailed explanation, but I hope it gives somewhat more insight. The short version is that you can ignore elements in destructuring by using no identifiers, and making your test pass by doing:

it('in for-of loop', () => {
  for (var [, a, b] of [[0, 1, 2]]) {}
  expect([a, b]).toEqual([1, 2]);
});

Hope this helps.

@leodutra
Copy link

leodutra commented Feb 19, 2018

This is handy when you want to destructure and redefine. Hat-trick!

function foo(param) {
    const arg = {a, b} = param
    console.log(a, b)
    console.log(arg)
}
foo({a:1, b:2})

@KangYoosam
Copy link

+1

@mqliutie
Copy link

can I set {a,b,c} to one object using pure JavaScript?

like

const {a,b,c} as params = obj; or const {a,b,c} : params = obj;

// in store.js

// long time you mustn't remember how many variables there
const obj = {a:1,b:2,c:3,d:4,e:5,f:6};

// in component

const {a,b,c} = obj;
getSomething({a,b,c});
// I know this way can do it . but long time you mustn't remember how many variables in obj
//const {d,e,f,...params} = obj;
// some day you add `y` to obj in the store and it will be in params if you don't update your component.
// getSomething(params);

@mikaelbr
Copy link
Author

@mqliutie I'm not entirely sure if I understand the question. Are you talking about constructing objects after destructuring it? Could you maybe show a more concise example?

@evandrix
Copy link

evandrix commented Apr 27, 2018

how about:

const data = {
  valid: true,
  products: [{
    item: 'Awesome shoe',
    price: 19.99,
    inStore: ['Regent Street', 'Oxford Street', 'Harrods']
  }, {
    item: 'Fave shirt',
    price: 12.99,
    inStore: ['Regent Street']
  }]
};
const { products: [ { item, inStore: [, , x] } ]} = data;
console.log(item + " is in " + x);

why is the output "Awesome shoe is in Harrods", and not "Fav shirt is in undefined" ?

@mikaelbr
Copy link
Author

@evandrix If I understand the question correctly, the key here is the destructuring of arrays. Let's simplify the code to see only the relevant parts:

const data = [{
  item: 'Awesome shoe',
  price: 19.99,
  inStore: ['Regent Street', 'Oxford Street', 'Harrods']
}, {
  item: 'Fave shirt',
  price: 12.99,
  inStore: ['Regent Street']
}];
const [ { item, inStore: [, , x] } ] = data;
console.log(item + " is in " + x);

Which would result in the same. We can reduce the structure further if we just focus on the item and not the inStore property:

const data = [{
  item: 'Awesome shoe'
}, {
  item: 'Fave shirt'
}];
const [ { item } ] = data;
console.log(item); //=> Awesome shoe

And that again is pretty much the same as:

const data = [1, 2];
const [ item ] = data;
console.log(item); //=> 1

And hopefully it's easier to see what is going on. We're destructuring an array and fetching the first element and ignore all other in the array. As if we did const [ item, ].

@AkshayIyer12
Copy link

Thanks! I'm using it production now. 👍

@wischli
Copy link

wischli commented Feb 5, 2019

Thank you! Another one I find nifty when iterating objects and requiring both the keys and values:

Object.entries({x: "xval", y: "yval"}).map(([key, val]) => console.log(key, val));
// x xval
// y yval

@peerreynders
Copy link

Thank You!

I was looking for a way to do this but it wasn't obvious to me that this particular approach would actually work:

const {prop01, prop01: { prop10 }} = {prop00: 0, prop01: {prop10: 10, prop11: 11}}
console.log(prop01, prop10)
// => {prop10: 10, prop11: 11} 10

// Example use case:
// const { request, request: { url }} = event

@caprica-Six
Copy link

caprica-Six commented Jul 8, 2019

... Let's simplify the code to see only the relevant parts:

const data = [{
  item: 'Awesome shoe'
}, {
  item: 'Fave shirt'
}];
const [ { item } ] = data;
console.log(item); //=> Awesome shoe

And that again is pretty much the same as:

const data = [1, 2];
const [ item ] = data;
console.log(item); //=> 1

And hopefully it's easier to see what is going on. We're destructuring an array and fetching the first element and ignore all other in the array. As if we did const [ item, ].

@mikaelbr - Very helpful, great explanation. Thank you!!!

@PeterWhittaker
Copy link

PeterWhittaker commented Apr 27, 2020

@mikaelbr, thanks, this is fantastic... ...but I have a question. I will keep it as brief as possible, so there will be a lot of ...

  • JS running in a web page reads a JSON file and uses JSON.parse(fileContent).
  • Some of the JSON strings in the file can contain special sequences, e.g., \n or \t.
  • When these are read in, they are preserved, right up until I pass the JSON to a class constructor and use destructuring to assign values to variables local to the object:
// ...
// myFileContents contains ..."firstArg": "split\nline",...
const myJSON = JSON.parse(myFileContents);
class myClassDoesSomeStuff {
    constructor (someArgs = {}) {
        const {
             firstArg = '',
             secondArg = '',
             // etc.
             ...
        } = someArgs;
       console.log(firstArg)
// ...
let someObject = new myClassDoesSomeStuff(myJSON);

firstArg is printed as

split
line

not as

split\nline

to get around this, I end up doing

let realFirst = JSON.stringify(firstArg).replace(/^""|""$/g, '"');

But that feels really gross, an ugly hack (but neither rude nor evil, so I can live with).

Is there a more elegant solution?

Thanks!

@PeterWhittaker
Copy link

I realized there is an error in my "sample code", the use of console.log, which will print out the newline no matter what:

console.log(firstArg)

In my actual code, values like firstArg are being assigned to a DOM element (a text input box). Without my stringify...replace trick, the \n gets converted, but with it, it is preserved, which is what I want. Sorry for the confusion.

@mikaelbr
Copy link
Author

Hi, @PeterWhittaker! I don't know if I understand your problem correctly, but when you have a newline character like this, console.log will output it with a line break in the console of your developer tool (as you said in your second comment). But when outputting in for instance DOM you won't get two lines in your HTML output. This is as HTML is not whitespace significant (for the most part**), but if you where to inspect the element tree using your developer tool you would see an actual new line in the source code. So the newline is there, but it isn't for instance a <br> which would cause the browser to render an actual new line.

But in input textarea (note <textarea>. not <input> as this is single line) you would have new lines. With a regular <input> field I belive you would just not see the newline.

Again, I don't really understand your problem. But if you wanted to keep the actual newline character inside your input field, without using textarea. You would have to "escape it". By escaping \n you would get \\n which is not a special character but a literal text representation of newline. To escape you can do a regex replace as you've shown, but there is no need for serializing through JSON. Google "escaping newline javascript" and you'll get tons of info and insight.

@PeterWhittaker
Copy link

PeterWhittaker commented Apr 28, 2020 via email

@mikaelbr
Copy link
Author

Read the last part of my comment above on escaping. If you want to keep \n as just literal text, you escape the new line character. Read this for instance: https://stackoverflow.com/questions/25921319/escape-new-lines-with-js

@PeterWhittaker
Copy link

PeterWhittaker commented Apr 29, 2020 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment