DEV Community

Paige Niedringhaus
Paige Niedringhaus

Posted on • Originally published at paigeniedringhaus.com on

Spread & Rest Parameters: JavaScript ES6 Feature Series (Pt 4)

Overhead view of hands holding highlighters over notes

Introduction

The inspiration behind these posts is simple: there are still plenty of developers for whom, JavaScript doesn’t make a whole ton of sense — what with its asynchronous behavior and being an interpreted language and all.

Add to that the yearly updates the ECMAScript committee is releasing, and it’s a lot of info to keep abreast of. 🤯 And check out this statistic below from Wikipedia.

As of May 2017 94.5% of 10 million most popular web pages used JavaScript. — JavaScript, Wikipedia

Since JS powers so much of the web, I wanted to provide a bunch of articles and examples of ES6+ features that I use regularly, for other developers to reference.

The aim is for these articles to be short, in-depth explanations about various improvements to the language, that I hope will inspire you to write some really cool stuff using JS. Who knows, you might even learn something new along the way. 😄

For my fourth post, I’ll be discussing rest parameters and the spread syntax: the most concise ways to condense or expand arguments, elements and object values with ease.


... Rest Parameters

Before I dive into rest parameters, I recommend you familiarize yourself with the three main types of functions in use in JavaScript today: function declarations, function expressions and arrow functions. I wrote a blog post about them, for a quick refresher.

Now that we’re on the same page, let’s get into what exactly rest parameters are.

Rest parameters are a new syntax that allows us to represent an indefinite number of arguments passed to a function as an array. Make sense? Not quite? 🤔 No problem, code examples usually help me grasp concepts easier too.

Anatomy of rest parameters in a function declaration

function f (x, y, ...a) {
  return (x + y) * a.length;
}

console.log(f( 1, 2, hello, true, 7)) // prints: 9
Enter fullscreen mode Exit fullscreen mode

When you’re looking at the example above, everything stays the same as with a normal function declaration except that the last argument declared, argument a has a suspicious ... in front of it. This is an example of a rest parameter.

What this means when the function executes is "take all remaining arguments being passed to this function, shove them all into an array, and do with them whatever the function body dictates in its statement body."

Here’s another example, but this time with an arrow function (which easily translates to a function expression in non-ES6 syntax, as well).

Rest parameters in an arrow function

const product = (e, f, ...g) => {
  return e * f + g.length; 
}

console.log(product(4, 7, 2, 6, 3)); // prints: 31
Enter fullscreen mode Exit fullscreen mode

Now at this point, you may have some questions about exactly how rest parameters can be used (I know I sure did), so let me lay out the rules for you.

Rest parameter rules, traits & use cases

Only the last param can have ...

No, I didn’t lose my train of thought there, what I’m saying is: only the last parameter defined in a function, can be prefixed with the ..., making it a rest parameter.

When the last parameter is prefixed with ..., all remaining (user supplied) arguments are placed within a "standard" Javascript array.

Output of arguments with a rest parameter

Here’s the kind of output you’ll see when you have a rest parameter as the final argument in a function call. As you can see, the first three arguments passed to myNumberFunction, the 1, 2, and 3 are printed out individually as the first three parameters defined in function: a, b, and c. All the remaining numbers passed to the rest parameter d, however, are printed out as an array [4, 5, 6, 7, 8].

function myNumberFunction(a, b, c, ...d) {
  console.log("a", a); // a 1
  console.log("b", b); // b 2
  console.log("c", c); // c 3
  console.log("...d", d); //...d [4, 5, 6, 7, 8]
}

myNumberFunction(1, 2, 3, 4, 5, 6, 7, 8);
Enter fullscreen mode Exit fullscreen mode

Rest parameters are not just an arguments object

There are three main differences to know about between the arguments object and new rest parameters.

  • Rest parameters are only the ones that haven’t been given a separate name (i.e. formally defined in function expression), while the arguments object contains all arguments passed to the function.
  • The arguments object is not a real array, while rest parameters are Array instances, meaning methods like sort(), map(), forEach(), or pop() can be applied on it directly.
  • The arguments object has additional functionality specific to itself (like the callee property).

Rest parameters were originally introduced to reduce the boilerplate code caused by arguments. Before, it was a major pain to convert arguments to a "normal array". Now, they can simply be passed in and then actioned upon.

Easy actioning on rest parameter arrays

function doubleUp(...simpleArgs){
  const arr = simpleArgs;
  const secondArr = arr.map(num => num * 2);
  return secondArr;
}

console.log(doubleUp(2, 6, 12, 18)); // [4, 12, 24, 36]
Enter fullscreen mode Exit fullscreen mode

Rest parameters can be destructured

One of my favorite new things about ES6 is array and object destructuring. I haven’t gone into detail about destructuring yet, but just wait, I will, before this series of articles is finished.

In a nutshell, destructuring makes it possible to unpack values from arrays, or properties from objects, into distinct variables. If you’re dying to learn more in the meantime, here’s a link to get more familiar with how destructuring works.

Moving on for the purposes of this post though, here’s rest parameters and destructuring at work.

Array destructuring with a rest parameter

const [captain, coCaptain, ...devs] = ["Bridget", "Joe", "Kyle", "Drew", "Patrick", "Francisco"];
console.log(captain); // "Bridget"
console.log(coCaptain); // "Joe"
console.log(devs); // ["Kyle", "Drew", "Patrick", "Francisco"]
Enter fullscreen mode Exit fullscreen mode

In the above declared array of variables, captain, coCaptain and devs, the rest parameter is applied to devs, so when the array of names is passed in, "Bridget" becomes the captain, "Joe" is the coCaptain and the devs are ["Kyle", "Drew", "Patrick", "Francisco"].

Handy, right? Just imagine the scenarios you’ve encountered where you either needed to pull out individual pieces of an array or keep other pieces together. Trust me, you’ll be so thankful for destructuring and rest params when you do.

The same type of thing can be done with object destructuring too.

Object destructuring with a rest parameter

const {pm1, pm2, ...restOfTeam} = {
  pm1: "Jeremy",
  pm2: "Tung",
  developer1: "Casey",
  developer2: "Mark",
  ux: "Christina"
};

console.log(pm1); // Jeremy
console.log(pm2); // Tung
console.log(restOfTeam); // { developer1: "Casey", developer2: "Mark", ux: "Christina" }
Enter fullscreen mode Exit fullscreen mode

This example deconstructs pm1 and pm2 from the rest of the team members in the object, and the remaining object properties: developer1, developer2, and ux are grouped together in a new object variable called restOfTeam.

Again, think about using this type of syntax to create new variables, objects, pull object properties apart into individual pieces, etc.

And those are the main things you need to know about rest parameters. At this point we can move on to the other use of ... in JavaScript, the spread syntax.

Spread syntax

While it resembles the rest parameter a great deal, the spread syntax has quite different uses.

The spread syntax allows an iterable such as an array or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.

Uh... What now?

Ok, that definition sounds technical, I know, believe me. But here’s what it means in practice:

If you’ve got a string, an array or an object, and you want to use all the values, you can spread all of them into function calls, new arrays, or new objects, with a very concise syntax.

The spread syntax at work in functions, arrays, strings, and objects

// For function calls:
function multiply(x, y, z) {
  return x * y * z;
}

const args = [1, 2, 3];

console.log(multiply(...args)); // 6

// For array literals or strings:
const iterableObj = [{protein: "steak"}, {carb: "potato"}, "milkshake"];
const randomList = [...iterableObj, "4", "five", 6];
console.log(randomList); // [{ protein: "steak" }, { carb: "potato" }, "milkshake", "4", "five", 6]
const str = "foo"
const chars = [...str] 
console.log(chars); // ["f", "o", "o"]

// For object literals (new in ECMAScript 2018):
const powerTool = { skuNumber: "996655", skuDescription: "Drill Bit" };
let secondPowerTool = { ...powerTool, toolDepartment: 25, toolClass: 7 };
console.log(secondPowerTool); // { skuNumber: "996655", skuDescription: "Drill Bit", toolDepartment: 25, toolClass: 7 }
Enter fullscreen mode Exit fullscreen mode

The different examples above show what happens when you spread various items.

For the first example, the multiply() function, I spread my args array into the function call, which simply takes the three values from the array (1, 2, and 3) and puts them in the places of the function’s accepted parameters x, y, and z.

In the case of the second and third examples, spreading arrays and strings, the original list, iterableObj, and the original string, "foo", are both spread into new variables, randomList and chars, respectively.

All the values in iterableObj are added to the new randomList array along with the new values assigned specifically to randomList. And the original string of "foo" is spread apart, character by character, into the new chars array.

The final example, only possible since the release of ECMAScript 2018, is of spreading the object powerTool's properties into a new object called secondPowerTool, and adding two new properties to that object as well: toolDepartment and toolClass.

Spread syntax rules, traits & use cases

Just like the rest parameter, the spread syntax is syntactic sugar that replaces complex methods and boilerplate code-filled ways of doing things that should be relatively easy.

That being said, there are things that you need to be aware of regarding spread.

Spread Replaces Apply in Function Calls

Up to now, it’s been common to use Function.prototype.apply() in cases where you want to use the elements of an array as arguments to a function.

Using apply() to assign array elements in a function call

function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction.apply(null, args);
Enter fullscreen mode Exit fullscreen mode

With spread, the syntax is much cleaner.

Spreading a list of array elements into a function call

function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction(...args);
Enter fullscreen mode Exit fullscreen mode

And something else that’s cool: any argument in the argument list can use spread syntax and it can be used multiple times.

Multiple spreads in a single function call

function myFunction(v, w, x, y, z) { }
var args = [0, 1];
myFunction(-1, ...args, 2, ...[3]);
Enter fullscreen mode Exit fullscreen mode

Spread syntax can be used with the new keyword

Unlike when calling a constructor with new, where it's not possible to directly use an array and apply, with spread, an array can be easily used with new.

Spread syntax with the new constructor

var dateFields = [1989, 3, 13];
var d = new Date(...dateFields);
console.log(d); // 13 Apr 1989
Enter fullscreen mode Exit fullscreen mode

Spread in array literals

Spread makes array manipulation so much easier, and methods like push(), concat(), and splice() a lot less necessary. Let’s go over what it can help you do.

Creating new array literals is a snap

Create a new array using an existing array as one part of it requires no additional array methods like it would have in the past.

Spreading one array into another new array

const fruits = ["watermelon", "peaches"];
const fruitBasket = ["apples", "grapes", ...fruits, "bananas", "kiwis", "mango"];
console.log(fruitBasket); // ["apples", "grapes", "watermelon", "peaches", "bananas", "kiwis", "mango"]
Enter fullscreen mode Exit fullscreen mode

How easy is that to add the fruits array to fruitsBasket? Super easy.

Copying arrays is a breeze too

With spread, copying the values of one array into another array is a piece of cake. Then you can continue to modify the newly copied array without affecting the original array (which lends itself well to the style of immutable, functional programming so popular today with frameworks like React and state management tools like Redux).

const arr1 = [1, 2, 3];

const arr2 = [...arr1];

console.log(arr2); // [1, 2, 3]

arr2.push(4);

console.log(arr1); // [1, 2, 3]

console.log(arr2); // [1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

Before I pushed 4 onto arr2, when the values were printed out, they were exactly the same as arr1. And once again, even after 4 is added to arr2, arr1 remains unaltered.

NOTE: Spread syntax only effectively goes one level deep while copying an array. Therefore, it may be unsuitable for copying multidimensional arrays, for that using a function like lodash’s _.cloneDeep() is recommended.

Concatenating arrays has never been easier

Array.prototype.concat() was the go-to when concatenating an array onto the end of an existing array. Without spread syntax this is done as:

.concat() Arrays Together

const germanCars = ["BMW", "Audi", "Mercedes"];
const japaneseCars = ["Honda", "Toyota", "Datsun"];

const concatCarMakers = germanCars.concat(japaneseCars);

console.log(concatCarMakers); // ["BMW", "Audi", "Mercedes", "Honda", "Toyota", "Datsun"]
Enter fullscreen mode Exit fullscreen mode

With spread syntax this becomes:

Spreading arrays together

const germanCars = ["BMW", "Audi", "Mercedes"];
const japaneseCars = ["Honda", "Toyota", "Datsun"];

const carMakers = [...germanCars, ...japaneseCars];

console.log(carMakers); // ["BMW", "Audi", "Mercedes", "Honda", "Toyota", "Datsun"]
Enter fullscreen mode Exit fullscreen mode

Spread syntax can also take the place of unshift() and make adding new elements to the front of an array much simpler than it used to be.

Spread elements into the beginning of an array

let numbers = [6, 5, 4];
const moreNumbers = [1, 2, 3];

numbers = [...moreNumbers, ...numbers];

console.log(numbers); // [1, 2, 3, 6, 5, 4]
Enter fullscreen mode Exit fullscreen mode

Spread in object literals

As of ECMAScript 2018, spread properties came to object literals. This lets an object copy its own enumerable properties from a provided object onto a new object.

Shallow cloning and merging objects is simple

While Object.assign() made shallow-cloning and merging objects together possible, using the spread syntax is even more concise.

// duplicate object properties
const markerSet = { copicMarkers: ["green", "blue", "red"]};
const duplicateMarkerSet = {...markerSet};

console.log(duplicateMarkerSet); // { copicMarkers: ["green", "blue", "red"] }

// merge two objects into a new one
const markerSet2 = { copicSketchMarkers: ["pink", "yellow", "orange"]};
const giantMarkerSet = { ...markerSet, ...markerSet2 };

console.log(giantMarkerSet); // { copicMarkers: ["green", "blue", "red"], copicSketchMarkers: ["pink", "yellow", "orange"] }
Enter fullscreen mode Exit fullscreen mode

It’s also worth noting that Object.assign() triggers setters, which binds an object property to a function to be called when there is an attempt to set that property, whereas the spread syntax does not.

Spread (aside from object properties) is only for iterables

The spread syntax (other than in the case of spread properties) can be applied only to iterable objects: Arrays, Maps, Sets, Strings and the like.

// spreading in an object to an array does NOT work
const obj = { key1: "value1"};
let array = [...obj];

console.log(array); // []

// spreading an array of objects into another array does work
const objInArray = [{ key2: "value2"}];
array = [...objInArray];

console.log(array); // [{ key2: "value2" }]
Enter fullscreen mode Exit fullscreen mode

And that’s about the long and short of the spread syntax details that you need to know to use it effectively.


Conclusion

At first glance, some of the newest JS syntax can seem totally foreign — even to people who’ve been writing JavaScript code for years. And while yes, it is different, it’s also incredibly powerful and makes doing our jobs so much easier than they were even just a few years ago.

My aim with this blog series is to explain some of the JavaScript and ES6 syntax you use everyday, and show you how to fully harness the newest parts of the JavaScript language to full effect.

Rest syntax looks exactly like spread syntax (...) but is used for destructuring arrays and objects. In a way, rest syntax is the opposite of spread syntax: spread expands an array into its elements, while rest collects multiple elements and condenses them into a single element. It’s so useful in so many common programming situations, as I’m sure you can already imagine.

Check back in a few weeks, I’ll be writing about more JavaScript and ES6 or something else related to web development.

If you’d like to make sure you never miss an article I write, sign up for my newsletter here: https://paigeniedringhaus.substack.com

Thanks for reading, I hope you’ll start seeing the possibilities of using rest parameters and the spread syntax in your code in the future — they make a myriad of things a cinch. Please share this with your friends if you found it helpful!


References & Further Resources

Top comments (0)