Calling a JavaScript function with variable arguments

I am working on this little Windows Scripting Host script using JavaScript where I basically need to load up a Word document and do a bunch of text transformation tasks on each line and dump the output to the console (which I plan to redirect to a file). I decided to employ the builder pattern a bit and set something up like this first:

//
// transform events collection
//
var transformTable = {
    parseFileBegin : [],
    parseLineBegin : [],
    parseLineEnd : [],
    parseFileEnd : []
};

The idea is to populate the arrays parseFileBegin and parseFileEnd with a set of function object references that would get called in sequence at the appropriate time. To make calling these callbacks easier I decided to come up with a fireEvent routine which I could then use to fire a particular set of callback functions. I wanted also, to be able to call fireEvent passing as many arguments as are needed for that particular callback. When invoking parseFileBegin for instance, I wanted to pass the name of the file as a parameter to the callback routines and when calling parseLineBegin I wanted to pass in a tokenized form of each line along with the line string itself. Here're a couple of examples of how I wanted to call fireEvent.

fireEvent(transformTable.parseFileBegin, fileName);
fireEvent(transformTable.parseLineBegin, line, tokens);

And here's what I came up with for fireEvent:

function forEach( arr, cb ) {
    for( var i = 0 ; i < arr.length ; ++i )
        if( cb( arr[i] ) == false )
            return false;

    return true;
}

function fireEvent(eventHandlers) {
    //
    // everything after the first argument must be
    // considered as parameters to be passed to the
    // event handler routines
    //
    var args = [];
    var i = 0;
    forEach(arguments, function( arg ) {
        if( i++ == 0 )
            return;
        args.push( arg );
    });

    //
    // iterate through the handlers collection and call one by one
    //
    forEach(eventHandlers, function(handler) {
        // TODO: call the handler
    });
}

I needed to somehow call the function referenced by handler and pass all the values in the args array as parameters to it. One way might have been to dynamically build a string of JavaScript code that calls handler and then have it executed by calling eval on the string. But I perferred a more direct method if one were available. As it turned out, one was in fact available in the form of the apply method on Function objects. Consider this code:

var foo = function(s1, s2) {
    alert( s1 + " - " + s2 );
}

There are a couple of ways you can invoke foo. You can call it as you normally would with functions or, alternatively, you can call the member method apply that all function objects posses. Here's an example:

var foo = function(s1, s2) {
    alert( s1 + " - " + s2 );
}

foo("ding", 20);                 // call like normal function
foo.apply( null, ["ding", 20] ); // call via "apply" method

The apply method requires you to supply 2 parameters, the first one indicates the object in whose scope the function must be invoked - which means that the function will be invoked as though it were a member function of that object. The effect of this is that the variable "this" within that function will refer to the object you pass as the first argument. If you pass null then it will execute like a global function. Here's an example:

var person = {
    name : "binga",
    age : 20
};

var print = function() {
    alert( this.name + " - " + this.age );
}

print.apply( person );

From within the function print here, the reference to "this" turns out to refer the first parameter that you pass to apply. So what happens if you called print like this?

print.apply( null );

As things tend to be in such cases, "this" becomes "undefined" from inside print.

The second parameter to apply is of course, the array of parameters that are to be passed to the function. So with this new information, fireEvent looks like this:

function fireEvent(eventHandlers) {
    //
    // everything after the first argument must be
    // considered as parameters to be passed to the
    // event handler routines
    //
    var args = [];
    var i = 0;
    forEach(arguments, function( arg ) {
        if( i++ == 0 )
            return;
        args.push( arg );
    });

    //
    // iterate through the handlers collection and call one by one
    //
    forEach(eventHandlers, function(handler) {
        handler.apply(null, args);
    });
}

Simple enough, when you know how to do it eh?!

comments powered by Disqus