Question

How do I write a Javascript function that accepts a variable number of parameters, and forwards all of those parameters to other anonymous functions?

For example, consider the scenario of a method that fires an event:

function fireStartedEvent(a,b,c,d,e,f,g,...) {
    for(var i = 0; i < startedListeners.length; i++) {
        startedListeners[i](a,b,c,d,e,f,g,...);
    }
}

Especially since I have an event factory that generates these fire methods, these methods have no interest in knowing how many parameters a given event or its handlers consume. So I have it hard-wired at 7 right now (a through g). If it's any less, no problem. If it's any more, they get cut off. How can I just capture and pass on all parameters?

Thanks.

(Using jQuery or any other Javascript framework is not an option here.)

Was it helpful?

Solution

Solving this requires knowledge of two JavaScript concepts.

The first is the special arguments local variable that can be used to access the function arguments without knowing their name and it works like an Array. However, arguments is not an Array, but is "array like" -- having properties named 0..n-1, where n is the number of arguments to the function and a length property -- object. A simple demonstration usage might be:

function f (a) { // can include names still, if desired
    // arguments instanceof Array -> false (exceptions to this?)
    var firstArg = arguments[0]
    // a === firstArg -> always true
    // iterate all arguments, just as if it were an Array:
    for (var i = 0; i < arguments.length; i++) {
        alert(i + " : " + arguments[i])
    }
}
f("a","b","c")

The second feature is Function.apply which will invoke a function with a specific context (this when it is called) with arguments that result from the expansion of an "array like" object. But see 1.

Thus, putting it together:

function fireStartedEvent() {
    for(var i = 0; i < startedListeners.length; i++) {
        // jQuery will often pass in "cute" things, such as a element clicked
        // as the context. here we just pass through the current context, `this`,
        // as well as the arguments we received.
        var arg = Array.prototype.slice.call(arguments)
        startedListeners[i].apply(this, args)
    }
}

1 While the ECMAScript specification only calls for an "array like" object, Function.apply does not universally work with an "array like" object and a number of common implementations require a proper Array object. The warning from the Function.apply link:

Note: Most browsers, including Chrome 14 and Internet Explorer 9, still do not accept array like objects and will throw an exception [if a non-Array object is passed]. [FireFox was fixed in version 4.]

Thankfully, there is a relatively simply idiom to turn an "array like" object into an Array (which is ironic because Array.slice does universally work with an "array like" object):

var args = Array.prototype.slice.call(arguments);

(And then args can be universally used in be used in Function.apply.)

OTHER TIPS

I think "apply" and "arguments" are two JavaScript concepts you can use here:

function fireStartedEvent() {
  for (var i = 0; i < startedListeners.length; i++) {
    startedListeners[i].apply(startedListeners[i], arguments);
  }
}

Here's some code from my Firebug console I tried this out with:

a = function(foo) { alert('a: ' + foo); };
b = function(foo, bar) { alert('b: ' + foo + ', ' + bar); };

startedListeners = [a, b];

function fireStartedEvent() {
  for (var i = 0; i < startedListeners.length; i++) {
    startedListeners[i].apply(startedListeners[i], arguments);
  }
}


fireStartedEvent('one', 'two');
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top