Ivan Zuzak – blog

Named arguments in JavaScript

Posted in web development by Ivan Zuzak on August 9, 2009

This blog has moved to a new domain. You can view this post here.

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:

// 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.
// 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

Advertisements

5 Responses

Subscribe to comments with RSS.

  1. lrbabe said, on August 10, 2009 at 2:22 pm

    I’m wondering if caching the results would be interesting.
    You could store them this way:

    fnCache.push({‘fn’: fn, ‘callParameters’: callParameters})

    and check if the function is already stored this way:
    var i, length = fnCache.length;
    for(i = 0; i < length; ++i)
    if(fn.constructor == fnCache[i].fn.constructor)
    {
    callParameters = fnCache[i].callParameters
    // and place the function at the beginning of the cache to find it faster next time
    if(i != 0)
    fnCache.unshift(fnCache.splice(i, 1));
    break;
    }

    Caching can be usefull if you use the same function often (in a loop for example). And then add an extra parameter to callWithNamedArgs to bypass the cache checking. Yeah, could do.

    • izuzak said, on August 11, 2009 at 10:22 am

      Hey Louis! Cool idea – caching almost always speeds things up. It’d be also possible to measure the gain from caching with a few simple test cases (string parsing is probably the most time-costly operation).

      Also, what do you think about making the cache a property of the Function object? That way it’d be possible to alleviate the need to pass the cache as an argument and even loop the cache:

      function callWithNamedArgs ( fn, self, namedParams ) {
      if (fn.argIndexesCache) {
      argIndexes = fn.argIndexesCache;
      } else {
      // parse argument indexes as in the original implementation and then store them into cache
      fn.argIndexesCache = argIndexes;
      }

      // map the dictionary of arguments to an array of arguments as in the original implementation
      }

  2. Paul Irish said, on August 10, 2009 at 3:51 pm

    I’ve done similar work when making a convenience wrapper for console.log: http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/

    But I found I really wanted to do:
    arguments.callee.caller.context

    But sadly that is not available. arguments.callee.caller.arguments is the trickiest thing you can do right now, but that’s going away in EcmaScript5 strict mode.

    I think you’re out of luck snagging context from inside. Also I do think this would be a little sexier if added onto the Function prototype. ;-)

    • izuzak said, on August 11, 2009 at 10:54 am

      Hi Paul, thanks for replying! That’s exactly what I wanted to do also :). JavaScript seriously lacks a reflection library and cutting arguments.callee will make things even worse.

      An idea that I was thinking about was storing the context of the Function object as a property of the object, at the time the object was created (by hacking into the constructor perhaps?). That way, it’d be easy to reference the context afterwards by doing:

      parentContext = fn.storedContext;

      However, this solution assumes that the context of a function is a static thing that doesn’t change, which I don’t think is generally true – you could create a function in the global namespace (the context at the point of creation is the global namespace), and then later add the function as a property to a specific object (the context of the function is now the specific object). What do you think?

  3. […] to make it better Posted in web development by izuzak on October 10, 2009 As promised in my post on named arguments in JavaScript, this post will be about a project I’ve been working on with Marko for the last few […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: