Ivan Zuzak home about blog projects talks & papers other

Named arguments in JavaScript

09 Aug 2009

While working on another project (pmrpc, which I will write about soon), the implementation of a feature came down to calling JavaScript functions with named arguments. Unfortunately, the JavaScript language (or EcmaScript to be exact) doesn’t support named arguments, rather only positional arguments.

Usually, JavaScript developers get around this by defining functions to accept a single object argument which acts like a container for all other named parameters:

1
2
3
4
5
6
7
8
9
10
// params is a container for other named parameters
function callMe(params) {
  var param1 = params['param1'];
  var param2 = params['param2'];
  var param3 = params['param3'];

  // do something...
}

var result = callMe({ 'param3' : 1, 'param1' : 2, 'param2' : 3 });

This solution works nicely when you are in the position to modify every function to be called this way. However, this is not always the case (e.g. when using external libraries), and also isn’t as readable as having a proper parameter list, and introduces code overhead in every function that implements this principle. What I needed for my project is something that enables calling any JavaScript function with named arguments, without modifying the function itself.

What I came up with (code presented below) is based on the following:

  1. using the toString method of the Function prototype, it is possible to retrieve a string representation of any function definition
  2. from the string representation, it is possible to parse the number, order and names of function parameters
  3. from a dictionary of named arguments, it is possible to construct an array of arguments in the correct order using the parsed representation
  4. using the apply method of the Function prototype, it is possible to dynamically call a function, passing an array of arguments instead of an argument list.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// calls function fn with context self and parameters defined in dictionary
// namedParams
function callWithNamedArgs( fn, self, namedParams ) {
  // get string representation of function
  var fnDef = fn.toString();

  // parse the string representation and retrieve order of parameters
  var argNames = fnDef.substring(fnDef.indexOf("(")+1, fnDef.indexOf(")"));
  argNames = (argNames === "") ? [] : argNames.split(", ");
  var argIndexes = {};
  for (var i=0; i<argNames.length; i++) {
    argIndexes[argNames[i]] = i;
  }

  // construct an array of arguments from a dictionary
  var callParameters = [];
  for (paramName in namedParams) {
    if (argIndexes[paramName]) {
      callParameters[argIndexes[paramName]] = namedParams[paramName];
    }
  }

  // invoke function with specified context and arguments array
  return fn.apply(self, callParameters);
}

// example function
function callMe(param1, param2, param3) {
  // do something ...
}

// example invocation with named parameters
var result = callWithNamedArgs(
               callMe, this, { 'param3' : 1, 'param1' : 2, 'param2' : 3 });

This could also have been implemented as a Function prototype method and be called directly on a function object, which would simplify usage since the target function parameter wouldn’t have to be passed. But, if the CallWithNamedArgs function indeed was implemented prototype function, could the context parameter be removed also? I.e. is it possible to retrieve the current execution context (this object) of the target function within the CallWithNamedArgs method? The problem is that the execution context within the CallWithNamedArgs method is the Function object on which the CallWithNamedArgs method was called. But what we need is the execution context of the target function, which is the Function object on which the CallWithNamedArgs method was called. That is, what we need is the current execution context of the parent object. Any ideas? @izuzak.

Comments