简体   繁体   中英

Can I bypass optional parameters and still set a rest parameter in Javascript?

I have a function with a required parameter (A), some optional parameters (B,C) and a rest parameter (Z)

const doTheThing = (a, b = 'B', c = 'C', ...z) => {
  console.log(a, b, c, z);
}

I have cases where I would like to call the function without specifying the optional parameters, but still specify the rest parameter "Z"

doTheThing('A', ...'Z');

Expected Output:

'A', 'B', 'C', 'Z'

Unfortunately, I get the following:

Parsing error: Shorthand property assignments are valid only in destructuring patterns

How do I go about solving this?

JavaScript doesn't allow supplying named parameters or any sort of parameter skipping, so it's not possible to do what you want with the function in its current form. Here are some alternatives, though:

Plain JavaScript approach: a configuration Object as parameter

Instead of accepting multiple parameters

func = (a, b, c) => { /* operate with parameters */ } 
func("One", "Two", "Three")

your function will instead accept an object

func = config => { /* operate with config */ }
func({a: "One", b: "Two", c: "Three"})

This is a common pattern in JavaScript because it allows you to almost name your variables and doesn't require you pass them in the correct order.. It makes it easy to pass a large quantity of them and it can also make it easy to default them, too.

 const doTheThing = (config) => { const defaultProperties = { b: "B", c: "C" } const {a, b, c, ...rest} = Object.assign({}, defaultProperties, config); const z = Object.values(rest); //extract their values, otherwise you get an object console.log(a, b, c, z); } doTheThing({a: "A", x: "X", y: "Y", z: "Z"}); 

It is slightly clunky to use with rest parameters but not unworkable.

However, it does mean that it may be harder to see what parameters you can pass and what is required, if you have a large number of them.

Object Oriented approach: Builder pattern

You create a builder object - it serves to hold values until you call the final method at which point it takes all parameters and constructs an object in one go.

This is how more Object Oriented languages handle having a multitude of parameters where you can even have some of them optional. It's not really common to see builders defined like this in JavaScript but it's not too strange, either. If you use classes already or even TypeScript, then this is probably a better fit.

 class DoTheThingBuilder { constructor() { this.a = null; this.b = "B"; this.c = "C"; this.z = null; } withA(a) { this.a = a; return this; } withB(b) { this.b = b; return this; } withC(c) { this.c = c; return this; } withEverythingElse(...z) { this.z = z; return this; } doTheActualThing() { const {a, b, c, z} = this; console.log(a, b, c, z); } } const builder = new DoTheThingBuilder(); builder .withA("A") .withEverythingElse("X", "Y", "Z") .doTheActualThing(); 

As you can see, this can be pretty verbose for some simple tasks. It is a big overkill for this example, but perhaps in actual usage, you might find it helps.

I've deviated a bit from the usual approach - normally, you would set all parameters needed with the builder and finally call .build() which constructs an object . In this case, I basically renamed build to doTheActualThing and it's executing the function.

Functional approach: Currying

The concept of currying is quite simple - instead of having one function that accepts several parameters

func = (a, b, c) => { /* operate with parameters */ }

you have a function that takes one parameter, that returns a function that takes the second parameter, that returns another function, etc., until all parameters are satisfied, at which point the full function is executed.

func = a => b => c => { /* operate with parameters */ }

In many ways, this is the functional equivalent of the OO Builder pattern.

 const doTheThing = (a) => (b = "B") => (c = 'C') => (...z) => console.log(a, b, c, z); doTheThing("A")()()("X", "Y", "Z"); 

This way you can skip the second and third parameter by not supplying them and you'd get the defaults. It's also way shorter than a builder. However, reading the function can be a bit weird.

That is not possible and very error-prone. The point of naming your parameters is to know what they are and in what order they are coming.

You could achieve something similar using object as a function parameter:

const doTheThing = ({ a, b = "B", c = "C", others = {} }) => {
  const params = { a, b, c, ...others }; // this will merge your parameters into one object
  console.log(params);
}

doTheThing({ a: "A", others: { z: "Z" }});

This will log A, B, C, Z. Demo: https://codepen.io/tomekbuszewski/pen/jQqmNL?editors=0011

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM