loading...
Cover image for Understanding Rest Parameter Syntax

Understanding Rest Parameter Syntax

laurieontech profile image Laurie Originally published at tenmilesquare.com ・3 min read

Welcome to Rest parameter syntax. It's like the spread operator!

...except not.

I want to say upfront that I'm not justifying the repeated use of the ... symbol in the JavaScript language. But hopefully, this post will help you understand how spread and rest are different yet similar.

The use case for rest

Let's say that you want to pass in an unknown number of arguments to a function. That's when you want to use the rest parameter!

function testRest(arg1, arg2, ...otherArgs) {
   console.log(arg1) // 1
   console.log(arg2) // 2
   console.log(otherArgs) // [3, 4, 5]

}

testRest(1, 2, 3, 4, 5)

In this example arg1 and arg2 are passed through as expected, then all of the additional arguments are added to the otherArgs array.

One of the benefits is that otherArgs is truly an array. That means all of your array functions are available.

You can look at the number of arguments inside of it.

function testRest(arg1, arg2, ...otherArgs) {
   const numOfArgs = otherArgs.length() // 3

}

testRest(1, 2, 3, 4, 5)

You can also manipulate it using array prototype functions. map, filter, reduce, flat, etc.

Single Extra Argument

One thing to keep in mind is that otherArgs will always create an array. That means that a single argument will be wrapped in an array.

function testRest(arg1, arg2, ...otherArgs) {
   console.log(arg1) // 1
   console.log(arg2) // 2
   console.log(otherArgs) // [3]

}

testRest(1, 2, 3)

Additionally, not including any additional arguments will result in an empty array.

function testRest(arg1, arg2, ...otherArgs) {
   console.log(arg1) // 1
   console.log(arg2) // 2
   console.log(otherArgs) // []

}

testRest(1, 2)

Remember destructuring?

If you need a quick refresh on destructuring assignment check out this post.

As it turns out, you can use destructuring and the rest parameter together.

function testRest(...[first, second, third]) {
   console.log(first) // 1
   console.log(second) // 2
   console.log(third) // 3

}

testRest(1, 2, 3)

Keep in mind that destructuring expects the arguments to be there. If you break that contract, you should know what to expect.

If you don't include a destructured expected argument, it will result in an undefined reference.

function testRest(...[first, second, third]) {
   console.log(first) // 1
   console.log(second) // 2
   console.log(third) // undefined

}

testRest(1, 2)

If you include an argument beyond what you've destructured, that argument will be dropped.

function testRest(...[first, second, third]) {
   console.log(first) // 1
   console.log(second) // 2
   console.log(third) // 3
   // 4 is not destructured

}

testRest(1, 2, 3, 4)

Conclusion

And there you have it! As you can see in the examples above the main difference between rest and spread is location.

Spread syntax can appear inside a function or when calling the function. Rest parameter syntax is limited to the function signature itself. Keep that in mind if you're trying to determine which is being used.

Hope this helped you understand the concepts better.

As always, if you're interested in concepts like this one, check out some of these posts:

Posted on by:

laurieontech profile

Laurie

@laurieontech

Software dev at Gatsby | DC techie | Conference speaker | egghead Instructor | TC39 Educators Committee | Girls Who Code Facilitator | Board game geek | @laurieontech on twitter

Discussion

markdown guide
 

I think rest has nothing to do with the function signature. It is more about object deconstruction and If you spread the whole object into something or if you partially deconstruct it and then access what ever is remaining, the "rest" or remainder.

const stuff = ['a', 'b', 'c', 'd', 'e']
const [foo, bar, ...rest] = stuff
console.log(rest) // ["c", "d", "e"]

As opossed to

const menuA = {wine: true, beer: true}
const menuB = {coke: true, pepsi: true}
const spread = {...menuA, ...menuB}
console.log(spread) // { beer: true, coke: true, pepsi: true, wine: true}

You could even use the rest and spread it into a new array.

const stuff = ['a', 'b', 'c', 'd', 'e']
const [foo, bar, ...rest] = stuff
const spread = [ ...['f', 'g'], ...rest]
console.log(spread) // ["f", "g", "c", "d", "e"]

So in conclusion, function signature is just one place where partial object deconstruction may occur.

 

Yup, certainly good examples. I opted for function signature since that’s a pretty common use case. But “the rest of” is a great way to think about it.

 

Personally I like the dual use of the ... for rest and spread. They're sort of symmetric operations, and it strikes me as rather intuitive and elegant to use the same symbol, and one you understand the contexts in which they're used (which I think you've explained very well), it feels natural. But that's just me, I'd be interested to hear a counter perspective!

 

I think once you know them that's absolutely true. But I wouldn't call it intuitive just based on readability. I'm also of the camp that ... was a poor choice. I can make sense of it, but it doesn't invoke much. Perhaps a keyword may have been better.

 

Out of curiosity, why are you choosing to use

function(...[arg1, arg2, arg3]) { //... }

over

function({ arg1, arg2, arg3 }) { //... }

?

Is there any technical differences or is this just syntactical preference?

 

The first example is using the rest parameter, which is what the article is meant to explain.

The second example would not apply for the example above since it's only passing integers. It would work if the example looked like this though.

function testRest({ first }) {
  console.log(first);
}
testRest({ first: 1 });

However, the "infinite" number of arguments use case is not handled unless the arguments in question are already in an object and their key names are known. It's a very different piece of syntax.

 

Good piece, thanks! I've known about the spread operator for a little while, but it hasn't yet made its way into my reach-for arsenal. I don't seem to remember to use it at the opportune times. Do you find there's a particular use case where it clicked for you?

 

Probably my most common use case is merging an array or object into something I'm defining. Like so:

const arr = [1,2,3,4]

const numbers = [...arr, 6,7,10]
 

You said that it always creates an array, right? Would the result be [[1,2,3,4], 6,7,10]?

...Oh, nevermind, just tried it in the console! It flattens it; that makes a lot of sense!

Yup! Check out this post if you want that example specifically. It's essentially grabbing each element in the array and dropping it into a new array structure.

 

Interesting! I have some ideas about use cases now. Definitely cleared up a couple of things.