Robin Maben
Robin Maben

Reputation: 23094

Is there a way to provide named parameters in a function call in JavaScript?

C#, for example, allows using named parameters like so:

calculateBMI(70, height: 175);

I find this quite useful. How can I get a similar effect in JavaScript?

I've tried doing things like

myFunction({ param1: 70, param2: 175 });

function myFunction(params){
  // Check if params is an object
  // Check if the parameters I need are non-null
  // Blah blah
}

but it seems awkward. Is there a simpler way?

Upvotes: 476

Views: 422228

Answers (15)

Hainan Zhao
Hainan Zhao

Reputation: 2092

Not exactly a solution, just a workaround. You can use eslint rules to enforce or auto fix your code with inline comments. eslint-plugin-param-inline-comments

function test(a, b, c, d) { }

// before
test(1, true, null, false);

// after eslint --fix
test(/* a */ 1, /* b */ true, /* c */ null, /* d */ false)

Upvotes: 0

pasx
pasx

Reputation: 2985

Here is my approach to initializing a rather complex class holding a lot of parameters. I am also a C# dev so it shows up in the syntax I used. The same would work for calling a function.

export class fruit {
    static apple = 'Apple'
    static banana = 'Banana'
...
}

export class Foo {
    constructor(params) {
        const p = params ? params : {}
        this.bar = p.bar ?? defaultBar
        this.fruit = p.fruit ?? fruit.Apple
    ...
    }
}

const foo = new Foo({
  bar: anotherBar,
  fruit: fruit.Banana
...
})

Calling the Foo constructor when creating a new Foo I pass it a new object where I can name the properties whatever I want. OFC this is not C# and this is error prone. One should consider checking the input for unmatched keys if this construct was widely used.

Upvotes: 0

Travis Heeter
Travis Heeter

Reputation: 14084

I had a similar problem, but for an Object. This is an oversimplification of the problem and solution:

Suppose I have a Car object:

function Car(miles = 0, images = [], axles = 2){
 this.miles = miles
 this.images = images
 this.axles = axles
 return this
}

But suppose when I create a new car, I don't have the images yet:

let car = Car(miles = 1000, axles = 3)  

Creating car like this, js does not read the param name and just assigns the values as they come in:

Car{ miles:1000, images:3, axles:2 }  // images should be [], and axles 3!

My solution is to create the object first, then assign the values:

let car = new Car()
car.miles = 1000
car.axles = 3

And now the params are assigned to the correct members:

Car{ miles:1000, images=[] ,axles=3 }

And this solves the IDE-not-knowing-param-names problem when you convert the parameters to an object.

So for your specific solution, you could do this (although it seems like overkill given the simplicity of our examples):

let mf = new MyFunction()
mf.param1 =  70
mf.param2 = 175

function MyFunction(param1=0,param2=0){
  this.param1 = param1
  this.param2 = param2
}

Upvotes: 1

Felix Kling
Felix Kling

Reputation: 817030

ES2015 and later

In ES2015, parameter destructuring can be used to simulate named parameters. It would require the caller to pass an object, but you can avoid all of the checks inside the function if you also use default parameters:

myFunction({ param1 : 70, param2 : 175});

function myFunction({param1, param2}={}){
  // ...function body...
}

// Or with defaults, 
function myFunc({
  name = 'Default user',
  age = 'N/A'
}={}) {
  // ...function body...
}

ES5

There is a way to come close to what you want, but it is based on the output of Function.prototype.toString [ES5], which is implementation dependent to some degree, so it might not be cross-browser compatible.

The idea is to parse the parameter names from the string representation of the function so that you can associate the properties of an object with the corresponding parameter.

A function call could then look like

func(a, b, {someArg: ..., someOtherArg: ...});

where a and b are positional arguments and the last argument is an object with named arguments.

For example:

var parameterfy = (function() {
    var pattern = /function[^(]*\(([^)]*)\)/;

    return function(func) {
        // fails horribly for parameterless functions ;)
        var args = func.toString().match(pattern)[1].split(/,\s*/);

        return function() {
            var named_params = arguments[arguments.length - 1];
            if (typeof named_params === 'object') {
                var params = [].slice.call(arguments, 0, -1);
                if (params.length < args.length) {
                    for (var i = params.length, l = args.length; i < l; i++) {
                        params.push(named_params[args[i]]);
                    }
                    return func.apply(this, params);
                }
            }
            return func.apply(null, arguments);
        };
    };
}());

Which you would use as:

var foo = parameterfy(function(a, b, c) {
    console.log('a is ' + a, ' | b is ' + b, ' | c is ' + c);     
});

foo(1, 2, 3); // a is 1  | b is 2  | c is 3
foo(1, {b:2, c:3}); // a is 1  | b is 2  | c is 3
foo(1, {c:3}); // a is 1  | b is undefined  | c is 3
foo({a: 1, c:3}); // a is 1  | b is undefined  | c is 3 

DEMO

There are some drawbacks to this approach (you have been warned!):

  • If the last argument is an object, it is treated as a "named argument objects"
  • You will always get as many arguments as you defined in the function, but some of them might have the value undefined (that's different from having no value at all). That means you cannot use arguments.length to test how many arguments have been passed.

Instead of having a function creating the wrapper, you could also have a function which accepts a function and various values as arguments, such as

call(func, a, b, {posArg: ... });

or even extend Function.prototype so that you could do:

foo.execute(a, b, {posArg: ...});

Upvotes: 473

Ray Perea
Ray Perea

Reputation: 5851

Lots of people say to just use the "Pass an object" trick so that you have named parameters.

/**
 * My Function
 *
 * @param {Object} arg1 Named arguments
 */
function myFunc(arg1) { }

myFunc({ param1 : 70, param2 : 175});

And that works great, except... when it comes to most IDEs out there, a lot of us developers rely on type / argument hints within our IDE. I personally use PhpStorm (along with other JetBrains IDEs, like PyCharm for Python and AppCode for Objective-C).

And the biggest problem with using the "Pass an object" trick is that when you are calling the function, the IDE gives you a single type hint and that's it... How are we supposed to know what parameters and types should go into the arg1 object?

I have no idea what parameters should go in arg1

So... the "Pass an object" trick doesn't work for me... It actually causes more headaches with having to look at each function's docblock before I know what parameters the function expects.... Sure, it's great for when you are maintaining existing code, but it's horrible for writing new code.

Well, this is the technique I use... Now, there may be some issues with it, and some developers may tell me I'm doing it wrong, and I have an open mind when it comes to these things... I am always willing to look at better ways of accomplishing a task... So, if there is an issue with this technique, then comments are welcome.

/**
 * My Function
 *
 * @param {string} arg1 Argument 1
 * @param {string} arg2 Argument 2
 */
function myFunc(arg1, arg2) { }

var arg1, arg2;
myFunc(arg1='Param1', arg2='Param2');

This way, I have the best of both worlds. New code is easy to write as my IDE gives me all the proper argument hints. And, while maintaining code later on, I can see at a glance, not only the value passed to the function, but also the name of the argument. The only overhead I see is declaring your argument names as local variables to keep from polluting the global namespace. Sure, it's a bit of extra typing, but it's trivial compared to the time it takes to look up docblocks while writing new code or maintaining existing code.

Now, I have all the parameters and types when creating new code

Update - 2022

JavaScript now has the ability to have something close to named parameters using object destructuring available in ES6. Most newer browsers can use this feature See browser support

This is how it works:


// Define your function like this
function myFunc({arg1, arg2, arg3}) {
    // Function body
}

// Call your function like this
myFunc({arg1: "value1", arg2: "value2", arg3: "value3"})

// You can also have default values for arguments
function myFunc2({firstName, lastName, age = 21}) {
    // Function body
}

// And you can call it with or without an "age" argument
myFunc({firstName: "John", lastName: "Doe"}) // Age will be 21
myFunc({firstName: "Jane", lastName: "Doe", age: 22})

The best part is that most IDE's now support this syntax and you get good argument hint support

TypeScript

For those of you using TypeScript, you can do the same thing using this syntax

function myFunc(
    {firstName, lastName, age = 21}:
    {firstName: string, lastName: string, age?: number}
) {
    // Function body
}

OR, using an interface

interface Params {
    firstName: string
    lastName: string
    age?: number
}

function myFunc({firstName, lastName, age = 21}: Params) {
    // Function body
}

Upvotes: 54

Mitya
Mitya

Reputation: 34596

No - the object approach is JavaScript's answer to this. There is no problem with this provided your function expects an object rather than separate params.

Upvotes: 103

user14950373
user14950373

Reputation:

Yes, well, kind of. I've found two solutions. I'll explain just one.

In this solution, we give up positional arguments, though.

We can use an object (almost identical to a dict in Python) to pass the arguments.

In this example, I'm using the function to generate the name of a image file:

// First we define our function with just ONE argument
function name_of_img(img_desc){

  // With this step, any undefined value will be assigned a value
  if(img_desc.size == undefined) {img_desc.size = "400x500"}
  if(img_desc.format == undefined) {img_desc.format = ".png"}

  console.log(img_desc.size + img_desc.format)
}

// Notice inside our function we're passing a dict/object

name_of_img({size: "200x250", format : ".jpg"})
// In Python name_of_img(size="200x250" , format="jpg")
// returns "200x250.jpg"

name_of_img({size: "1200x950"})
// In Python name_of_img(size="1200x950")
// returns "1200x950.png"

We can modify this example, so we can use positional arguments too, we can also modify it so non valid arguments can be passed, I think I will make a GitHub repository about this.

Upvotes: 0

Schalton
Schalton

Reputation: 3104

This is admittedly pseudocode, but I believe it'll work (I know it works in TypeScript; I'm adopting it for JavaScript).

// Target Function
const myFunc = (a=1,b=2,c=3) => {a+b+c}

// Goal usage:
myFunc(a=5, b=6) // 14
myFunc(c=0) // 3

// Set your defaults
const myFuncDefaults = {a:1, b:2, c:3};

// Override them with passed parameters
const myFuncParams = (params) => { return Object.assign(myFuncDefaults, params)}


// Use the overloaded dict as the input
const myFunc2 = (params) => {
  let {a, b, c} = myFuncParams(params);
  return myFunc(a, b, c)
}

// Usage:
myFunc({a:5, b:6}) // 14
myFunc({c:0}) // 3

// Written more succinctly:
const myFunc = (params) => {
  let {a,b,c} = Object.assign({a:1, b:2, c:3}, params)
  return a + b + c
}

For what it's worth, TypeScript makes this kind of nice with hinting:

interface IParams {
  a: number;
  b: number;
  c: number;
}

const myFunc = (params: Partial<IParams>): number => {
  const default: IParams = {a:1, b:2, c:3};
  let {a, b, c} = Object.assign(default, params)
  return a + b + c
}

Upvotes: 3

dav_i
dav_i

Reputation: 28147

If you want to make it clear what each of the parameters are, rather than just calling

someFunction(70, 115);

do the following:

var width = 70, height = 115;
someFunction(width, height);

Sure, it's an extra line of code, but it wins on readability.

Upvotes: 30

jgran
jgran

Reputation: 1284

NB. My answer of 2016 is not correct and misleading as mentioned in comments.

Trying Node-6.4.0 ( process.versions.v8 = '5.0.71.60') and Node Chakracore-v7.0.0-pre8 and then Chrome-52 (V8=5.2.361.49), I've noticed that named parameters are almost implemented, but that order has still precedence. I can't find what the ECMA standard says.

>function f(a=1, b=2){ console.log(`a=${a} + b=${b} = ${a+b}`) }

> f()
a=1 + b=2 = 3
> f(a=5)
a=5 + b=2 = 7
> f(a=7, b=10)
a=7 + b=10 = 17

But order is required!! Is it the standard behaviour?

> f(b=10)
a=10 + b=2 = 12

Upvotes: 1

Vince Spicer
Vince Spicer

Reputation: 4520

Coming from Python this bugged me. I wrote a simple wrapper/Proxy for node that will accept both positional and keyword objects.

https://github.com/vinces1979/node-def/blob/master/README.md

Upvotes: 1

Dmitri Zaitsev
Dmitri Zaitsev

Reputation: 14056

Calling function f with named parameters passed as the object

o = {height: 1, width: 5, ...}

is basically calling its composition f(...g(o)) where I am using the spread syntax and g is a "binding" map connecting the object values with their parameter positions.

The binding map is precisely the missing ingredient, that can be represented by the array of its keys:

// map 'height' to the first and 'width' to the second param
binding = ['height', 'width']

// take binding and arg object and return aray of args
withNamed = (bnd, o) => bnd.map(param => o[param])

// call f with named args via binding
f(...withNamed(binding, {hight: 1, width: 5}))

Note the three decoupled ingredients: the function, the object with named arguments and the binding. This decoupling allows for a lot of flexibility to use this construct, where the binding can be arbitrarily customized in function's definition and arbitrarily extended at the function call time.

For instance, you may want to abbreviate height and width as h and w inside your function's definition, to make it shorter and cleaner, while you still want to call it with full names for clarity:

// use short params
f = (h, w) => ...

// modify f to be called with named args
ff = o => f(...withNamed(['height', 'width'], o))

// now call with real more descriptive names
ff({height: 1, width: 5})

This flexibility is also more useful for functional programming, where functions can be arbitrarily transformed with their original param names getting lost.

Upvotes: 3

user234461
user234461

Reputation: 1215

Contrary to what is commonly believed, named parameters can be implemented in standard, old-school JavaScript (for boolean parameters only) by means of a simple, neat coding convention, as shown below.

function f(p1=true, p2=false) {
    ...
}

f(!!"p1"==false, !!"p2"==true); // call f(p1=false, p2=true)

Caveats:

  • Ordering of arguments must be preserved - but the pattern is still useful, since it makes it obvious which actual argument is meant for which formal parameter without having to grep for the function signature or use an IDE.

  • This only works for booleans. However, I'm sure a similar pattern could be developed for other types using JavaScript's unique type coercion semantics.

Upvotes: -3

Allen Ellison
Allen Ellison

Reputation: 67

There is another way. If you're passing an object by reference, that object's properties will appear in the function's local scope. I know this works for Safari (haven't checked other browsers) and I don't know if this feature has a name, but the below example illustrates its use.

Although in practice I don't think that this offers any functional value beyond the technique you're already using, it's a little cleaner semantically. And it still requires passing a object reference or an object literal.

function sum({ a:a, b:b}) {
    console.log(a+'+'+b);
    if(a==undefined) a=0;
    if(b==undefined) b=0;
    return (a+b);
}

// will work (returns 9 and 3 respectively)
console.log(sum({a:4,b:5}));
console.log(sum({a:3}));

// will not work (returns 0)
console.log(sum(4,5));
console.log(sum(4));

Upvotes: 2

Udo Klein
Udo Klein

Reputation: 6912

Another way would be to use attributes of a suitable object, e.g. like so:

function plus(a,b) { return a+b; };

Plus = { a: function(x) { return { b: function(y) { return plus(x,y) }}}, 
         b: function(y) { return { a: function(x) { return plus(x,y) }}}};

sum = Plus.a(3).b(5);

Of course for this made up example it is somewhat meaningless. But in cases where the function looks like

do_something(some_connection_handle, some_context_parameter, some_value)

it might be more useful. It also could be combined with "parameterfy" idea to create such an object out of an existing function in a generic way. That is for each parameter it would create a member that can evaluate to a partial evaluated version of the function.

This idea is of course related to Schönfinkeling aka Currying.

Upvotes: 7

Related Questions