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?!