Instantly share code, notes, and snippets.

@yang-wei /README.md
Last active Dec 10, 2018

Embed
What would you like to do?
ES6 destructing (and rest parameter)

We will first discussed how destructing and rest parameters can be used in ES6 - in arrays and objects. Then we will look at a few examples and also discuss some quiz.

arrays

var array = [1, 2, 3, 4];
var nestedArray = [1, 2, 3, 4, [7, 8, 9]];

var [a, b, c, d] = array;
console.log(a, b, c, d)
// -------- 1  2  3  4

var [a, , , d, [x, y, z]] = nestedArray;
console.log(a, d, x, y, z)
// -------- 1  4  7  8  9

We don't have to match the full array

var [a, b, c] = array;
console.log(a, b, c)
// -------- 1  2  3

// rest parameter
var [a, b, ...c] = array;
console.log(c);
// [3, 4]

However when using rest parameter, you must apply it as the last argument.

var [...head, d] = array;
// Uncaught SyntaxError: Unexpected token...

When destructing variable exceeds its matched array, undefined is returned

var [a, b, c, d, e] = array;
console.log(e);
// undefined

But we can always back it up by set its default value

var [a, b, c, d, e = 5] = array;
console.log(e);
// -------- 5

We can clone array easily

var [...clonedArray] = array;
console.log(clonedArray);
// [1, 2, 3, 4]

// clone the nested one
var [,,,, [...clonedNestedArray]] = nestedArray;
console.log(clonedNestedArray);
// [7, 8, 9]

Swap variable like a boss

var a = 1, b = 2;
[b, a] = [a, b];

If you had writing some JavaScript before, here is how you used to access arguments passed into a function.

function foo() {
  return Array.prototype.slice.call(arguments, 0);
}

foo(1,2,3,4,5) // [1, 2, 3, 4, 5]

We can now refactor it into a shorted code:

function foo(...args) {
  return args;
}

foo(1,2,3,4,5) // [1, 2, 3, 4, 5]

Object

var object = {a: "A", b: "B", c: "C"};
var nestedObject = {a: "A", b: "B", c: "C", x: {y: "Y", z: "Z"}};

var {a: A, b: B, c: C} = object;
console.log(A, B, C);
// ------ "A" "B" "C"

var {a: A, b: B, c: C, x: X} = nestedObject;
console.log(X);
// {y: "Y", z: "Z"}

When we only need a value

var {b: B} = object;
console.log(B);
// ------- "B"

Similar to array, we can set a default value

var {b: B, d: D = "D"} = object;
console.log(B, D);
// ------- "B" "D"

// and it returns undefined when keys are not match
var {a: A, b: B, d: D} = object;
console.log(A, B, D);

For everytime we have to write { keys: newVariable } explicit this is very verbose. Let's enter short hand method:

var {a, b, c} = object;
console.log(a, b, c);
// -------"A" "B" "C"

It might seems confuse at first but this is the short hand of

var {a: a, b: b, c: c} = object;
// new variables a, b, c are created

Do not use short hand method if you want to create different variable name.

Continueing on other examples:

var object = {a: "A", b: "B", c: "C"};
var nestedObject = {a: "A", b: "B", c: "C", x: {y: "Y", z: "Z"}};


var {a, b, c, x} = nestedObject;
console.log(x);
// { y: "Y", z: "Z" }


var {b} = object;
console.log(b);
// ------- "B"

var {b, d = "D"} = object;
console.log(d, d);
// ------- "B" "D"

// and it returns undefined when keys are not match
var {a, b, d} = object;
console.log(a, b, d);
// ------- "A" "B" undefined

Using rest in object destructing failed =(

// error
// var {a: A, b: B, c: ...C} = object;
// console.log(A, B, C);   <-- error
// use the shorthand method

var {a, b, ...c} = object;  // es7
console.log(c);
// {c: "C"}

// be careful with some reserved property name

var [length] = ["a", "b", "c"];
console.log(length)
// 1 because a.length = 1

We used to write long code like this:

var John = {name: "John", age: 20};
var Marry = {name: "Marry"};

function getAge(person) {
  age = person.age || 18 // default value
  return age
}
getAge(John);  // 20
getAge(Marry); // 18
// with es6 
function getAge({age = 18}) {
  return age
}

Example

Let's see some pratical usage. Eg: optional arguments for functions

function sum(...numbers) {
  return numbers.reduce((n, total) => {
    return n + total
  }, 0);
}
sum();   // 0
sum(1, 2, 3);   // 6
sum(1, 2, 3, 4, 5);   // 15

// more abstraction <-- out of topic
function math(equation, ...numbers) {
  return numbers.reduce((n, total) => {
    return equation.call(null, n, total);
  });
}

const add = (a, b) => { return a + b; }
let sum1 = math(add, 1, 2, 3, 4);
// 10

const times = (a, b) => { return a * b; }
let product1 = math(times, 1, 2, 3)
// 6

Example on OSS

Let's look at some example on some great open source project - redux. In redux source code there is a utils function - compose which is used to turn our verbose code into a nicer one.

function (arg) { return fn1(fn2(fn3(arg))); }

into

function(arg) { compose(fn1, fn2, fn3)(arg) }

Here is the shorten source code and explaination

export default function compose(...funcs) {   // here, a list of function(s) is expected: fn1, [fn2, fn3...]
  return (...args) => { // it return a composed function which expects a list of argument(s): arg1, [arg2, arg3... ]
    // args is now an array: args = [arg1, arg2, ...]
    const last = funcs[funcs.length - 1]
    const rest = funcs.slice(0, -1)
    
    return rest.reduceRight((composed, f) => f(composed), last(...args)) 
    // the first part in reduce right is not so interesting, we just keep chaining functions
    // in the second part - last(...args)
    // since our last function will accept argument like last(arg1, arg2...) instead of last([arg1, arg2...])
    // we turn [arg1, arg2, ...] into arg1, arg2, ...
    // by using rest
  }
}

To simplify how the args being changed:

function inspect(...args) {
  // when this function is called
  // args = ["a", "b", "c"]
  
  console.log(args)  // ["a", "b", "c"]
  console.log(...args) // "a" "b" "c"
}
inspect("a", "b", "c")

Quiz

@kangax published an ES6 JavaScript quiz and there are fews related to destructing and rest. Let's have fun with them.

Question 3

let x, { x: y = 1 } = { x }; y;

Let break it down piece by piece:

let x, // x = undefined
   { x: y = 1 } = { x }
   // here a new variable y is created with default value 1
   // but since x is undefined on right hand side
   // we can think of it as
   // { x: y = 1 } = { }
   y; // when we call this y returns 1

Question 7

[...[...'...']].length

First I was scared by the syntax. But the '...' is actually a string. To make it clear:

[...[...'str']]

Since string is iterable so ...'str' will give us ["s", "t", "r"]. Then the next spread [...["s", "t", "r"]] will give us ["s", "t", "r"]. So the answer is clear.

Question 11

((...x, xs)=>x)(1,2,3)

As stated above in the array section, rest parameter only can be applied as the last argument. So this will throw us error.

Conclusion

That's all for now, I hope you enjoy the discussion.

@peksipatongeis

This comment has been minimized.

peksipatongeis commented Jul 12, 2018

First of all, nice gist!

Using rest in object destructing failed =(

// error
// var {a: A, b: B, c: ...C} = object;
// console.log(A, B, C); <-- error
// use the shorthand method

Related to the piece of code above, destructuring with rest operator should work just fine. It just works a bit differently as you are already naming the rest with the operator.

So you could have this instead:

let { a: A, b: B, ...C } = object

where console.log(A, B, C) outputs A B {c: "C"}

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