Reputation: 81801
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.)
Upvotes: 6
Views: 821
Reputation:
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
.)
Upvotes: 8
Reputation: 10857
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');
Upvotes: 5