Parsing JavaScript Function Argument Names

Lately, I have been experimenting with various JavaScript MVCs, and I was surprised when AngularJS threw an error, when I changed the argument name in a controller function declaration using a factory resource. The library was obviously doing some magic to parse the names of the arguments of a function and validating it against a previously defined namespace. This was interesting, so I thought I would discover how they were doing it and some possible applications.

How do it…

There are some hacky ways of doing this, which use the arguments.callee and .caller properties, but they are deprecated and may become unavailable in the future, and several approaches which use eval. However it is best to avoid eval and any deprecated feature. The last approach is to use .toString, which is simple and has full cross-browser supported.

Here is a function to get the names of the arguments of a function instance:

function getParamNames(fn) {
    var funStr = fn.toString();
    return funStr.slice(funStr.indexOf('(') + 1, funStr.indexOf(')')).match(/([^\s,]+)/g);
}

This snippet shows the output of passing a test function in:

function fnTest(a, b, c, d) {}
getParamNames(fnTest) === ['a', 'b', 'c', 'd'];

One application of getParamNames might be to check that an argument name is used. The following checks the provided function for an arbitrary number of argument names, returning true or false:

function assertFunctionDefines(fn) {
    var aFuncParamNames = getParamNames(fn),
        oFuncParamNames = {},
        i = aFuncParamNames.length - 1;

    while (i >= 0) {
        oFuncParamNames[aFuncParamNames[i]] = 1;
        i -= 1;
    }

    i = arguments.length - 1;

    while (i > 0) {
        if (!oFuncParamNames[arguments[i]]) {
            return false;
        }
        i -= 1;
    }

    return true;
}

This shows a couple of examples using the test function again:

assertFunctionDefines(fnTest, 'a', 'b', 'c', 'd') === true;
assertFunctionDefines(fnTest, 'g') === false;

Lastly, you might want to use the keys of an object to determine which argument should be assigned where when calling a function:

function function_kwargs(fn, o) {
    var argNames = getParamNames(fn),
        nameIndexMap = {},
        i = argNames.length - 1,
        args = [];

    while (0 <= i) {
        nameIndexMap[argNames[i]] = i;
        i -= 1;
    }

    for (i in o) {
        if (o.hasOwnProperty(i)) {
            if (undefined !== nameIndexMap[i]) {
                args[nameIndexMap[i]] = o[i];
            }
        }
    }

    return fn.apply(this, args);
}

Executing the following:

function_kwargs(fnTest, {a: 'foo', c: 'bar'});

Would pass a='foo', b=undefined, c='bar', and d=undefined into the test function.

Here is a jsfiddle showing each of the above functions.

How it works…

The getParamNames function calls the .toString method of the function instance, which will return the function code as a string. It then slices the string between the parentheses and uses a regex to create an array of all words separated by spaces and commas. These words are the argument names of the function. This technique works on all modern browsers, although I have not checked on IE < 9.

The assertFunctionDefines function simply creates a map of all the known arguments and then tests if the provided argument names are in the map, returning true if they all are, or false if one or more are not. There are nicer ways in jQuery for searching arrays, but I wanted the function to be library independent.

Lastly, the function_kwargs maps the keys of the object to its position in the arguments of the provided function. Again, I used native looping, so that the code is library independent. I think this function may have some real practical use for handling configurations objects. Consider the following two simple examples:

Classical Configuration


function MyClass(data, conf) {
    this.data = data;
    this.configuration(conf || {});
}
MyClass.prototype = {
    configuration: function(conf) {
        this.config = {
            option1: conf.option1 || default1,
            option2: conf.option2 || default2,
            ...
        };
    }
}

function_kwargs Configuration


function MyClass(data, conf) {
    this.data = data;
    function_kwargs.call(this, this.configuration, conf || {});
}
MyClass.prototype = {
    configuration: function(option1, option2) {
        this.config = {
            option1: option1 || default1,
            option2: option2 || default2,
            ...
        };
    }
}

We can explicitly define the known options on the configuration function, which requires slightly less code, but is the real benefit is, IMHO, that the code is more obviously documented and easier to read.