将onchange函数事件附加到变量上

6

最近我开始学习闭包和匿名函数,我想知道我的代码是否正确(它可以工作!):

newInput.onchange = function(x){
     return function(){
          PassFileName(x);  
     }
}(counter);

所以这是在一个循环中,"保存"当前的'counter'值(1、2、3...)。如果没有return函数,'counter'将始终是'counter'的最后一个值。

我的代码是否正确?或者有更好的方法来"捕获"当前计数器并将其附加到onchange事件上吗?

谢谢!


这种方法需要注意的一点是,会创建并持续存在于函数作为监听器时无用的闭包。确保外部函数不会不必要地保留对庞大对象或变量的引用。实现可能(有些确实会)在外部作用域中没有对其它内容的引用时将它们优化掉,但在IE中曾经是创建内存泄漏的好方法。 - RobG
2个回答

5

是的,你的理解是正确的,但为了与实现的最大兼容性,你需要在函数周围加上括号:

    newInput.onchange = (function(x){
//                      ^--- here
         return function(){
              PassFileName(x);  
         }
    })(counter);
//   ^--- and here

每当你使用函数表达式并立即调用它时,你需要使用那些括号,否则会有一个解析歧义。
更新:虽然你的方法是可以的,但值得指出的是,它有点浪费。;-) 它在每次循环迭代中创建了两个函数,外部匿名函数和内部匿名函数。这两个函数都会保留下来(除非实现优化,你知道一些引擎不会有)。相反,你可以在每个循环中只创建一个加上一个工厂函数。
// In the loop
newInput.onchange = makeHandler(x);

// Outside the loop
function makeHandler(x){
     return function(){
          PassFileName(x);  
     };
}

有些人认为这样更容易阅读(我也是),只要makeHandler仍然足够靠近循环,你就不会迷失方向。
使用工厂函数还可以为您提供在作用域中不关闭任何其他内容的机会,尽管那时您必须将工厂函数放得更远(例如,在一个良好包含的范围内)。
您还可以考虑使用通用的柯里化函数,例如Prototype提供的函数。一个不传递调用时参数的通用curry看起来像这样:
function curry(f) {
    var args = arguments;
    return function() {
        f.apply(undefined, args);
    };
}

但通常情况下,拥有传递运行时参数的解释器会更加有用(但更昂贵)。这是一种简单粗暴的方法(没有进行优化;通过优化可以显著降低调用时间开销):

function curry(f) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        f.apply(undefined,
                args.concat(Array.prototype.slice.call(arguments)));
    };
}

在这两种情况下的优点是,你没有关闭任何可以避免的新内容。
离题:此外,从技术上讲,你依赖于分号插入(在你的return语句的末尾应该有一个分号),我总是不建议依赖它。在这个例子中非常安全,但还是要小心。;-)

3
您正在进行的操作是相当标准的,没有什么问题。然而,我更喜欢一种类似的方法:
(function (x) {
    newInput.onchange = function () {
        PassFileName(x);
    };
})(counter);

对我而言,通过将整个代码块(缩进)包裹起来更清晰明了,因为我是有意为之创建新作用域以捕获变量的值。


同意,这样更清晰明了。不过,参考@RobG上面所说的,用这种方式实现几乎肯定是不能优化掉外部函数的,因为它(现在)在其变量对象上有内部函数需要的东西(即x参数)。而@wata的方法至少给了引擎优化多余工厂函数的机会... - T.J. Crowder
@T.J. 我同意创建过多的函数,你的 makeHandler 更新肯定是更好的选择(+1!),如果你的需求超出了一些快速而肮脏的代码。但我不明白自动优化如何在我的示例和 @wata 的示例之间有所不同? - David Tang
@Box9:是的,这很棘手。 :-) 当您调用函数时,会创建一个 执行上下文。 执行上下文具有关联的 变量对象,该对象具有每个参数、每个 var 和每个函数 声明 的结果的属性(不包括函数表达式)。该变量对象放置在作用域链的顶部,该链用于解析未经修饰的引用(如 x)。这就是闭包的工作原理,函数保留对变量对象的引用(该对象反过来引用链中的下一个对象等)。*(续)* - T.J. Crowder
@Box9:*(继续)* 在@wata的版本中,调用外部函数的变量对象将是空的,但在您的版本中,它将具有用于外部函数的x参数的x属性。优化器更有可能能够发现可以丢弃具有空变量对象(和变量对象、执行上下文)的函数,而不是如果变量对象上有某些东西。当我说“空白”时,变量对象从未 真正 空白(它至少有一个名为arguments的属性),但我们可以说,除了它总是有的东西,其他都是空白的 :-) - T.J. Crowder
@T.J. 谢谢你的解释。我明白你关于变量对象的说法,但是在 @wata 的版本中,外部函数也有一个参数 x。那么为什么在这种情况下变量对象是“空”的呢? - David Tang
@Box9: “所以为什么在这种情况下变量对象是“空白”的?”呃,嗯,它不是。我完全弄反了。 :-)由于某种原因,我认为@wata的外部函数没有该参数,但当然它有,并且必须有。对此很抱歉,没有任何区别。叹气 不过,这还是一个不错的学术讨论,对吧? - T.J. Crowder

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