87

I have a function like this:

function foo(a, b, c, d, e, f) {
}

In order to call this function only with an f argument, I know I should do:

foo(undefined, undefined, undefined, undefined, undefined, theFValue);

Is there a less verbose way to do this?

5
  • 1
    Is the function written by you? Can you change it? Commented Sep 11, 2015 at 8:10
  • 3
    You could use a single custom object as parameter instead a list of different parameters. Commented Sep 11, 2015 at 8:11
  • @Juhana Yes, is my own function Commented Sep 11, 2015 at 8:46
  • FWIW, The var _ = undefined; foo(_,_,_,_,_, theFValue); solution is buried in this long answer. You would of course do the initial declaration of _ once, in some common utility file that you include in every js file. Commented Oct 27, 2020 at 0:30
  • I could have sworn you could just do foo( , , , , , fValue); but I see that it is not possible. Commented Jun 29, 2022 at 18:21

9 Answers 9

85

Such:

foo(undefined, undefined, undefined, undefined, undefined, arg1, arg2);

.is equal to:

foo(...Array(5), arg1, arg2);

.or:

foo(...[,,,,,], arg1, arg2);

Such:

foo(undefined, arg1, arg2);

.is equal to:

foo(...Array(1), arg1, arg2);

.or:

foo(...[,], arg1, arg2);

Such:

foo(arg1, arg2);

.is equal to:

foo(...Array(0), arg1, arg2);

.or:

foo(...[], arg1, arg2);
Sign up to request clarification or add additional context in comments.

6 Comments

Where can I read more about the syntax of [,,,,,] and what it means?
that will just returns an array with undefined elements, ie. [undefined, undefined, undefined, undefined, undefined]
@silkfire this is called spread operator
@silkfire Grammar and types - Array Literals see the "Extra commas in array literals" section.
My personal preference: foo(...[,,,,,"f"]). Or just refactoring the code to never have functions with that many optional arguments in the first place.
|
26

You could use apply:

foo.apply(this, Array(5).concat([theFValue]));

In this case, 5 is the amount of parameters you want to skip.

Wrap that in a function:

function call(fn, skipParams, parameter) {
    fn.apply(this, Array(skipParams).concat([parameter]));
}

call(foo, 5, theFValue);

However, in that case the scope of this is different, so you may need to pass that, too:

function call(fn, skipParams, parameter, thisArg) {
    fn.apply(thisArg, Array(skipParams).concat([parameter]));
}

call(foo, 5, theFValue, this);

Then again, this implementation only allows 1 parameter to be passed. Let's improve that:

function call(fn, skipParams, parameters, thisArg) {
    fn.apply(thisArg, Array(skipParams).concat(parameters));
}

call(foo, 5, [theFValue, theGValue, theHValue], this);

That's starting to get a "little" verbose. It also doesn't handle missing parameters after the first parameter that well, unless you want to pass undefined:

call(foo, 5, [theFValue, theGValue, theHValue, undefined, theJValue], this);

Or, something completely different:

var _ = undefined;
foo(_,_,_,_,_, theFValue);

On a more serious note:

Your best option to deal with optional parameters, is to change the way you're handling parameters. Simply pass an object:

function foo(parameters){
    // do stuff with `parameters.a`, `parameters.b`, etc.
}

foo({c: 1, g: false});

This approach doesn't suffer from any of the drawbacks in the earlier examples.

7 Comments

More simple would be foo.apply(this, [,,,,,theFValue]). Nice idea with the wrapping function, but it would slightly classier to make that a HOF which takes just fn and skipParams as arguments and returns a function that can be called with parameter.
Then the amount of parameters skipped isn't variable.
ideally the call should be foo(f: false), like in C#... perhaps will be possible in the future...?! )
Something like that is possible in ES6, @Serge.
@Cerbrus: the link is about default arguments, not exactly about optional or named arguments that could be skipped...
|
25

A better way to deal with optional arguments is to pass an object whose attributes you look up:

function foo(options) {
    var a = options.a,
        b = options.b,
        c = options.c,
        d = options.d,
        e = options.e,
        f = options.f;
}

foo({ f: 15 });

8 Comments

The object approach is the least verbose (the var stuff is optional) and most flexible one.
the only minor note is that the users should be aware about the parameter internal names "a, b, c" etc, that is not "visible" in say, intellisense helper...
Some might want to check if the argument is actual object: if (typeof options !== 'object') throw new Error('INVALID_TYPE: options is not "object"');
Note, it would be cleaner to use object destructuring here: const {a, b, c, d, e, f} = options
@serge You can document the parameters so intellisense is aware. @param {Object} params then @param {number} params.f - Number representing ...
|
10

Skip function:

const skip = (num) => new Array(num);

Skipping beginning params:

foo(...skip(4), f);

Skipping end params:

foo(f, ...skip(4));

Skipping middle params:

foo(f, ...skip(4), f2);

1 Comment

BTW, "Skipping end params" is not needed. foo(f) works in JS - params missing at end are undefined.
4

If you will pass an object with a property name f so you can use destructuring assignment with ES6 syntax like this:

function foo({ f }) {
  console.log(f);
}
    
foo({ g: 5, f: 10 });

2 Comments

Keep in mind that from this moment you cannot call function without parameters i.e. foo(), you must now call it foo({}).
As @AlexG.P. mentioned: you should change ({ f }) to ({ f } = {}} in order to omit the error occuring when foo() is invoked.
4

I provide some methods that may help you achieve, as below,

  1. Destructuring assignment (recommend)
  2. Optional_chaining

Method1: Destructuring assignment

Example1

function Person(name, {id="007", age=-1, info={msg:null, mood:undefined}}) {
  return [name, id, age, info.msg, info.mood]
}

// 👇 Test Only
for (const [result, expected] of [
  [Person("Carson", {}), // If you don't need any options then must set "" or {}
    ["Carson", "007", -1, null, undefined]
  ],
  [Person("Aoo", {
    age: 29,
    info: {
      msg: "hello world"
    }
  }),
    ["Aoo", "007", 29, "hello world", undefined]
  ],
  [Person("Boo", {
    id: "003",
    info: {
      mood: "Happy"
    }
  }),
    ["Boo", "003", -1, null, "Happy"]
  ]
]) {
  console.log(JSON.stringify(result))
  console.log(JSON.stringify(result) === JSON.stringify(expected))
}

Example 2

const user = {
  id: 42,
  displayName: 'jdoe',
  fullName: {
    firstName: 'John',
    lastName: 'Doe'
  }
};

function userId({id}) {
  return id;
}

function whois({displayName, fullName: {firstName: name}}) {
  return `${displayName} is ${name}`;
}

console.log(userId(user)); // 42
console.log(whois(user));  // "jdoe is John"

👆 source code from object_destructuring search Unpacking fields from objects passed as a function parameter

Method2

Use Optional_chaining to set the default value

const val = obj ?? "default value" // if obj is undefined then val = default value
const val = obj?.msg // equal to obj.msg if {msg:...} exists in the obj. Otherwise, undefined

for example

/*
Assume your options is:
{
  id:"",
  info:{
    msg:"",
    mood: "",
  }
}
*/
function MyFunc(name, options = {}) {
  const id = options.id ?? "007"
  const msg = options.info?.msg ?? null
  const mood = options.info?.mood
  // ...
}

Example

function Person(name, options = {}) {
  const id = options.id ?? "007"
  const msg = options.info?.msg ?? null
  const mood = options.info?.mood
  return [name, id, msg, mood]
}


for (const [result, expected] of [
  [Person("Carson"),
    ["Carson", "007", null, undefined]
  ],
  [Person("Aoo", {
    info: {
      msg: "hello world"
    }
  }),
    ["Aoo", "007", "hello world", undefined]
  ],
  [Person("Boo", {
    id: "003",
    info: {
      mood: "Happy"
    }
  }),
    ["Boo", "003", null, "Happy"]
  ]
]) {
  console.log(JSON.stringify(result) === JSON.stringify(expected))
}

Method 2.extend

If you want the IDE to know what the options is, you may consider using the below method,

function PersonOptions(options={}) {
  this.id = options.id ?? "007"
  this.msg = options.info?.msg ?? null
  this.mood = options.info?.mood
}

function Person2(name, options = new PersonOptions()) {
  return [name, options.id, options.msg, options.mood]
}

for (const [result, expected] of [
  [Person2("Carson"),
    ["Carson", "007", null, undefined]
  ],
  [Person2("Aoo", new PersonOptions({
    info: {
      msg: "hello world"
    }
  })),
    ["Aoo", "007", "hello world", undefined]
  ],
  [Person2("Boo", new PersonOptions({
    id: "003",
    info: {
      mood: "Happy"
    }
  })),
    ["Boo", "003", null, "Happy"]
  ]
]) {
  console.log(JSON.stringify(result) === JSON.stringify(expected))
}

Comments

3

If this is something you're going to want to do often, then consider a simple wrapper:

function bar(f) {
    foo(undefined, undefined, undefined, undefined, undefined, f);
}

If you're only doing this once, or you're wanting a random permutation of the parameters then this approach isn't the best.

Comments

1

Use bind for a partial application:

function foo(a, b, c, d, e, f) {
    document.write(f);
}

function skip(f, n) {
    while (n--) {
        f = f.bind(null, undefined);
    }
    return f;
}

skip(foo, 5)('hallo');

5 Comments

compare my "ideal" readability: draw(height: 5) vs skip(draw, 5)(5)
it is not valid javascript. what do you mean with that?
I mean your skip works, but it has a bad readability. For someone did't code is hard to understand what the code does. The called function is "skip", when should be "draw"
well, it works. any other presented solutions has not better readability nor functionality. should i delete my answer?
no, thank you for your answer. Is good, but I find the sookie's answer has a little bit better readability: the foo function is directly called...
1

How about

function multiply(a = 2, b = 1) {
  return a * b;
}

console.log(multiply(undefined, 3));
// expected output: 6

If you pass a param undefined, it will use the default value from the definition.

1 Comment

The question shows use of undefined, and asks for a less verbose way to write that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.