83

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?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
serge
  • 13,940
  • 35
  • 121
  • 205

9 Answers9

79

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);
Pacerier
  • 86,231
  • 106
  • 366
  • 634
  • 6
    Where can I read more about the syntax of `[,,,,,]` and what it means? – silkfire Nov 13 '19 at 14:48
  • 6
    that will just returns an array with undefined elements, ie. [undefined, undefined, undefined, undefined, undefined] – Tharzeez Jan 07 '20 at 09:44
  • @silkfire this is called spread operator – besthost Feb 12 '22 at 20:05
  • 1
    @silkfire [Grammar and types - Array Literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#array_literals) see the "Extra commas in array literals" section. – 3limin4t0r Jun 14 '22 at 13:24
  • My personal preference: `foo(...[,,,,,"f"])`. Or just refactoring the code to never have functions with that many optional arguments in the first place. – Domino Nov 23 '22 at 07:30
  • I would like to explain this for future readers: First, `Array(n)` creates an Array of length `n` with undefined items (see next parenthesis), or `[,,,,,]` creates an array containing five `undefined` items (technically not, but close enough). The spec states that at most one trailing comma is ignored, and missing expressions are replaced with undefined (see previous parenthesis). Then, the `...` operator spreads out the items of the array into arguments. (Edited for slight elaboration) – PoolloverNathan Feb 19 '23 at 19:55
25

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.

Cerbrus
  • 70,800
  • 18
  • 132
  • 147
  • 3
    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`. –  Sep 11 '15 at 08:15
  • 1
    Then the amount of parameters skipped isn't variable. – Cerbrus Sep 11 '15 at 08:16
  • ideally the call should be `foo(f: false)`, like in C#... perhaps will be possible in the future...?! ) – serge Sep 11 '15 at 09:11
  • Something like that [is possible in ES6](http://ariya.ofilabs.com/2013/02/es6-and-default-argument.html), @Serge. – Cerbrus Sep 11 '15 at 09:14
  • 2
    @Cerbrus: the link is about default arguments, not exactly about optional or named arguments that could be skipped... – serge Sep 11 '15 at 09:28
  • Automatically skip parameters that are not actually used by the function – xgqfrms Sep 28 '19 at 05:17
  • BTW: If `var _ = undefined; foo(_,_,_,_,_, theFValue);` solution was a separate answer, I would upvote it. As it is, it is buried in a long answer, most of which I don't feel inclined to upvote. – ToolmakerSteve Oct 27 '20 at 00:28
24

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 });
Michael Laszlo
  • 12,009
  • 2
  • 29
  • 47
  • 2
    The object approach is the least verbose (the `var` stuff is optional) and most flexible one. – Sebastian Simon Sep 11 '15 at 08:18
  • 5
    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... – serge Sep 11 '15 at 09:31
  • Some might want to check if the argument is actual object: `if (typeof options !== 'object') throw new Error('INVALID_TYPE: options is not "object"');` – Artfaith Sep 06 '21 at 09:34
  • Note, it would be cleaner to use object destructuring here: `const {a, b, c, d, e, f} = options` – Gabriel Garrett May 13 '22 at 00:23
  • @serge You can document the parameters so intellisense is aware. `@param {Object} params` then `@param {number} params.f - Number representing ...` – 3limin4t0r Jun 14 '22 at 12:51
  • @3limin4t0r could you elaborate in an answer? ) – serge Jun 14 '22 at 13:54
  • @serge See the JSDoc documentation: https://jsdoc.app/tags-param.html Most intellisense helpers pick up JSDoc and provide it to you while coding. – 3limin4t0r Jun 14 '22 at 14:01
  • It would be even cleaner to do the object destructuring like so: `function foo({a, b, c, d, e, f}) {` – Kirill Jun 16 '22 at 15:23
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);
Qtax
  • 33,241
  • 9
  • 83
  • 121
sookie
  • 2,437
  • 3
  • 29
  • 48
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 });
Idan Dagan
  • 10,273
  • 5
  • 34
  • 41
  • 3
    Keep in mind that from this moment you cannot call function without parameters i.e. `foo()`, you must now call it `foo({})`. – Alex G.P. Sep 26 '18 at 14:04
  • 4
    As @AlexG.P. mentioned: you should change `({ f })` to `({ f } = {}}` in order to omit the error occuring when `foo()` is invoked. – Hlib Derbenov Apr 09 '20 at 15:11
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.

Tro
  • 897
  • 9
  • 32
3

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))
}
Carson
  • 6,105
  • 2
  • 37
  • 45
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');
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • compare my "ideal" readability: `draw(height: 5)` vs `skip(draw, 5)(5)` – serge Sep 12 '18 at 08:42
  • it is not valid javascript. what do you mean with that? – Nina Scholz Sep 12 '18 at 08:45
  • 1
    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" – serge Sep 12 '18 at 08:46
  • well, it works. any other presented solutions has not better readability nor functionality. should i delete my answer? – Nina Scholz Sep 12 '18 at 08:49
  • 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... – serge Sep 12 '18 at 08:52
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.

Triguna
  • 504
  • 6
  • 13