40
votes

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?

Solutions:
I selected some proposed solutions (without using helper third functions)

// zero - ideal one, actually not possible(?!)
foo(f: fValue);

// one - asks a "strange" declaration
var _ = undefined;
foo(_, _, _, _, _, fValue);

// two - asks the {} to be used instead of a 'natural' list of args
//     - users should be aware about the internal structure of args obj
//       so this option is not 'intellisense friendly'
function foo(args){
    // do stuff with `args.a`, `args.b`, etc.
}    
foo({f: fValue});
9
Is the function written by you? Can you change it?JJJ
You could use a single custom object as parameter instead a list of different parameters.Davide Pastore
@Juhana Yes, is my own functionserge
@Serge, The answer is here: stackoverflow.com/a/44622678/632951Pacerier
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.ToolmakerSteve

9 Answers

36
votes

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);
20
votes

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.

17
votes

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
votes

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);
3
votes

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 });
1
votes

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.

1
votes

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');
0
votes

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.

0
votes

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))
}