如何编写一个接受并“转发”可变数量参数的JS函数?

6
如何编写一个接受可变数量参数的Javascript函数,并将所有这些参数转发给其他匿名函数?
例如,考虑触发事件的方法的情况:
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,...);
    }
}

特别是因为我有一个事件工厂来生成这些触发方法,这些方法不关心给定事件或其处理程序消耗多少参数。所以现在我已经硬编码为7个(a到g)。如果参数少了,没问题。如果参数多了,它们会被截断。我该如何捕获并传递所有参数?
谢谢。
(在这里使用jQuery或任何其他JavaScript框架不是一个选项。)

在JavaScript中,是否可以将参数作为数组传递?这是可能的。 - Cyclone
2个回答

8

解决这个问题需要了解两个 JavaScript 概念。

第一个是特殊的 arguments 局部变量,它可以用于访问函数参数而不知道它们的名称,并且它的工作方式类似于数组。然而,arguments 不是一个 Array,但是它是“类似于数组”的--具有命名为 0..n-1 的属性,其中 n 是函数参数的数量和一个 length 属性--对象。一个简单的演示用法可能是:

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")

第二个特性是Function.apply,它将使用特定的上下文(即调用时的this)以及由"类数组"对象扩展而来的参数来调用函数。但请参见1
因此,综合起来:
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虽然ECMAScript规范只要求一个“类数组”对象,但Function.apply并不普遍适用于“类数组”对象,许多常见的实现需要一个正确的Array对象。来自Function.apply链接的警告:

注意:大多数浏览器(包括Chrome 14和Internet Explorer 9)仍然不接受类数组对象,并会抛出异常[如果传递的是非Array对象]。 [Firefox在版本4中修复了此问题。]

幸运的是,有一个相对简单的习语可以将“类数组”对象转换为Array(这很讽刺,因为Array.slice普遍适用于“类数组”对象):

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

(然后args可以普遍地在Function.apply中使用。)

似乎不适用于提问者想要的内容。 - mauris
5
我认为这正是提问者想要的。 - bcat
1
@Andrew 没事,我并没有这么深入的参与;-) 但请注意,Lauri提供的示例代码是不正确的。它在一些浏览器中可能有效,但是它存在缺陷。 - user166390
1
Lauri 给了你鱼,但我认为 pst 教会了你如何使用钓竿。 - David Snabel-Caunt
1
啊,我讨厌浏览器特定的行为。我改用将参数定义为var args = Array.prototype.slice.call(arguments);并调用apply,这样它在更多的浏览器中都能正常工作。感谢你额外的提示,pst。 - Andrew Arnott
显示剩余2条评论

5
我认为"apply"和"arguments"是两个JavaScript概念,你可以在这里使用它们。
function fireStartedEvent() {
  for (var i = 0; i < startedListeners.length; i++) {
    startedListeners[i].apply(startedListeners[i], arguments);
  }
}

这是我从Firebug控制台中提取的一些代码,我用它来尝试:
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');

这段代码不正确。arguments是类似数组的对象,而不是真正的“数组”。apply方法需要一个数组作为第二个参数。 - user166390
看起来对我来说是可以工作的,因为使用 apply(null, arguments) 调用的方法被调用并且似乎能够识别所有不同的参数。我是否遗漏了什么问题? - Andrew Arnott
@pst - 我不知道这个,你有没有一个例子能够证明我发布的函数无法工作? - Lauri Lehtinen
1
@Lauri Arg。你挑战了我的说法(并且赢了;它在Safari、Chrome、FF和IE6-8中都可以工作):我找不到反例。改成了点赞。谢谢。 - user166390

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接