ES6 for Drupal Developers: Spread, Default Values, and Destructuring

  • 9 minute read

Beyond variable scope, there are other important considerations when assigning values to variables, and that’s where ES6’s new capabilities for default values and destructured assignment come into play. In addition, juggling variables of a particular type can be challenging when writing functions. In ES6, the spread (or rest) operator allows us to reinterpret arrays when using them in function arguments by casting them to a series of arguments, something that was nontrivial in ES5.

In the first installment of this blog series, we set up a workflow that allows us both to transpile arbitrary ES6 into JavaScript legible to all modern browsers and to bundle that JavaScript into browser-ready assets. We also explored how working with variable scope has improved in ES6 and working with let and const statements. In this second installment, we’ll be digging deeper into assigning values to variables as well as the spread (or rest) operator.

The spread or rest operator

In the past, converting an array into a series of arguments destined for use in a function required some gymnastics, usually inside the function itself. But this required using memory unnecessarily with a temporary single-use variable.

// ES5
var args = [1, 2, 3, 4, 5];
function foo(args) {
  var a = args[0],
      b = args[1],
      c = args[2],
      d = args[3],
      e = args[4];
  console.log(a, b, c, d, e);
}
foo(args); // 1 2 3 4 5

With the spread (or rest) operator, you can “spread” an array out to its constituent values for use in function arguments.

// ES6
function foo(a, b, c, d, e) {
  console.log(a, b, c, d, e);
}
foo(...[1, 2, 3, 4, 5]); // 1 2 3 4 5

Since strings are arrays, the spread operator works predictably in reverse with strings as well.

function bar(str) {
  console.log([...str]);
}
bar("foo"); // ["f", "o", "o"]

You can also use the spread operator to gather a set of values into an array of the “rest” of the values (hence the alternate name). In this case, we are gathering the remainder of the arguments supplied to this function into an array.

function baz(a, b, ...c) {
  console.log(a, b, c);
}
baz(1, 2, 3, 4, 5); // 1 2 [3, 4, 5]

One of the places where the spread operator is most useful is if you have functions with an arbitrary number of arguments that you want to put into an easily traversed array.

function arrayify(...args) {
  console.log(args);
}
arrayify("foo", "bar", "baz"); // ["foo", "bar", "baz"]
arrayify(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

Default parameters

In ES5, we would assign default values to function arguments by using the logical OR operator, but this comes with several drawbacks due to issues of type coercion. For instance, in the fourth invocation of foo() below, a is assigned a value of 1 because 0 is “falsy”. This has the potential to silently break our code if we don’t have adequately robust testing in place.

// ES5
function foo(a, b, c) {
  a = a || 1;
  b = b || 2;
  c = c || 3;
  console.log(a + b + c); // 6
}

foo();           // 6
foo(3, 5, 8);    // 16
foo(null, 5, 8); // 14
foo(0, 5, 8);    // 14, not 13 (0 is falsy)

Now, you can set default parameters when defining the arguments themselves.

// ES6
function foo(a = 1, b = 2, c = 3) {
  console.log(a + b + c); // 6
}

foo(null, 5, 8); // 13, not 14 (null coerces to 0)
foo(0, 5, 8);    // 13

Default values in parameters can also invoke other functions, since they are expressions.

function foo(arg) {
  return x + arg;
}
function bar(a = x + 10, b = foo(a)) {
  console.log(a, b);
}

var x = 3;
bar(); // 13 16
bar(5); // 15 18

Destructuring assignment

In this ES5 case, we need to manually assign array members and object properties to a temporary variable in order to access them as variables not in an array.

function arrayFoo() {
  return [1, 3, 5];
}
function objectBar() {
  return { x: 2, y: 4, z: 6 };
}

// ES5
var tmpArray = arrayFoo(),
    a = tmpArray[0], b = tmpArray[1], c = tmpArray[2],
    tmpObj = objectBar(),
    d = tmpObj.x, e = tmpObj.y, f = tmpObj.z;

console.log(a, b, c); // 1 3 5
console.log(d, e, f); // 2 4 6

In ES6, you can use destructuring assignment, meaning there is no need for additional temporary variables.

function arrayFoo() {
  return [1, 3, 5];
}
function objectBar() {
  return { x: 2, y: 4, z: 6 };
}

// ES6
var [a, b, c] = arrayFoo();
var {x: d, y: e, z: f} = objectBar();

console.log(a, b, c); // 1 3 5
console.log(d, e, f); // 2 4 6

If the property names match the variable names you want, you can abbreviate this even further. The longer syntax will use the name in the property value as the variable name.

function objectFoo() {
  return { x: 1, y: 3, z: 5 };
}
function objectBar() {
  return { a: 2, b: 4, c: 6 };
}

var {x, y, z} = objectFoo();
console.log(x, y, z); // 1 3 5
var {a: d, b: e, c: f} = objectBar();
console.log(d, e, f); // 2 4 6

You can use destructuring assignment to give new values to variables.

function arrayFoo() {
  return [1, 3, 5];
}
function objectBar() {
  return { a: 2, b: 4, c: 6 };
}

var obj = {};
[obj.a, obj.b, obj.c] = arrayFoo();
( { a: obj.x, b: obj.y, c: obj.z } = objectBar() );

console.log(obj.a, obj.b, obj.c); // 1 3 5
console.log(obj.x, obj.y, obj.z); // 2 4 6

You can also use default value assignment jointly with destructuring assignment.

function arrayFoo() {
  return [1, 3, 5];
}
function objectBar() {
  return { x: 2, y: 4, z: 6 };
}

var [a = 4, b = 8, c = 12, d = 16] = arrayFoo();
var {x = 5, y = 10, z = 15, w = 20} = objectBar();

console.log(a, b, c, d); // 1 3 5 16
console.log(x, y, z, w); // 2 4 6 20

Conclusion

As you can see, broadening your JavaScript toolset to include the spread operator, default parameters, and destructuring assignment opens the door to a variety of compelling ways to manipulate values and construct functions. Though the lessened need to use temporary variables may seem like a small win, even minimal improvements when summed together can unleash an otherwise unperformant large codebase.

In this second installment of this blog series, we explored the spread (or rest) operator, a way to provide parameter sets of arbitrary length to functions; default parameters, a more bulletproof approach to providing default fallbacks for function arguments; and destructuring assignment, a powerful tool to assign values to variables based on where they fall in preordained structures. In the third installment of this series, we’ll dive into some of the most conspicuous syntactic features of ES6, including arrow functions, concise properties and methods, and more.

New to this series? Check out the first part of the "ES6 for Drupal developers" blog series. And then continue on to part three. This blog series is a heavily updated and improved version of “Introduction to ES6”, a session delivered at SANDCamp 2016. Special thanks to Matt Grill for providing feedback during the writing process.