如何将 JS 变量的值(不是引用)传递给函数?

IT技术 javascript closures pass-by-reference listener pass-by-value
2021-01-25 21:20:47

这是我尝试运行的简化版本:

for (var i = 0; i < results.length; i++) {
    marker = results[i];
    google.maps.event.addListener(marker, 'click', function() { 
        change_selection(i);
    }); 
}

但我发现每个侦听器都使用 results.length 的值(for 循环终止时的值)。如何添加侦听器,以便每个侦听器在添加时都使用 i 的值,而不是对 i 的引用?

6个回答

在现代浏览器中,您可以使用letorconst关键字来创建块范围变量:

for (let i = 0; i < results.length; i++) {
  let marker = results[i];
  google.maps.event.addListener(marker, 'click', () => change_selection(i));
}

在较旧的浏览器中,您需要创建一个单独的作用域,通过将变量作为函数参数传递来将其保存在当前状态:

for (var i = 0; i < results.length; i++) {
  (function (i) {
    marker = results[i];
    google.maps.event.addListener(marker, 'click', function() { 
      change_selection(i);
    }); 
  })(i);
}

通过创建匿名函数并使用变量作为第一个参数调用它,您将按值传递给函数并创建一个闭包。

你要添加var之前marker不污染全局命名空间。
2021-03-19 21:20:47
使用谷歌的地图 API 后,我们可以安全地打赌标记的范围在 for 循环之外。很好的抓住安迪。
2021-03-19 21:20:47
@ThiefMaster:奇怪的是,我在一段时间内第一次看到这个答案后也想到了同样的事情。但是,查看 OP 的代码,我们不能完全确定它marker不是一个全局变量。
2021-03-22 21:20:47
@John:JSLint 的过分警告之一,IMO。遵守 Crockford 编写 JavaScript 的法则是完全可选的,这就是为什么我使用 JSHint 并在大多数警告中假设我可能不理解我正在编写的代码关闭。可悲的是,这是几个星期以来第二次有人在我的一个答案中提出这个问题,但幸运的是,你并没有投票给我,以迫使其他人遵守 Crockford 的编码理念. ;-)
2021-04-01 21:20:47
我同意这种方法有效,但 JSLint 反对在循环内创建函数。您可以在循环外创建函数,正如 James Allardice 在jslinterrors.com/dont-make-functions-within-a-loop 中所示
2021-04-10 21:20:47

除了闭包,您还可以使用function.bind

google.maps.event.addListener(marker, 'click', change_selection.bind(null, i));

i调用时将 in的值作为参数传递给函数。null用于 binding this,在这种情况下您不需要。)

function.bind由 Prototype 框架引入,并在 ECMAScript 第五版中标准化。在浏览器都原生支持它之前,您可以function.bind使用闭包添加自己的支持:

if (!('bind' in Function.prototype)) {
    Function.prototype.bind= function(owner) {
        var that= this;
        var args= Array.prototype.slice.call(arguments, 1);
        return function() {
            return that.apply(owner,
                args.length===0? arguments : arguments.length===0? args :
                args.concat(Array.prototype.slice.call(arguments, 0))
            );
        };
    };
}
刚刚注意到这一点,+1。我非常喜欢bind并且迫不及待地等待本机实现的推出。
2021-03-17 21:20:47
哪些浏览器支持这个?有没有手机浏览器?
2021-03-18 21:20:47
@NoBugs:目前:IE9+。Fx4+,最近的 Chrome 和 Opera 版本。不是 Safari,不是 iPhone,Android 浏览器从 Ice Cream Sandwich 开始就有了。
2021-04-02 21:20:47

关闭:

for (var i = 0, l= results.length; i < l; i++) {
    marker = results[i];
    (function(index){
        google.maps.event.addListener(marker, 'click', function() { 
            change_selection(index);
        }); 
    })(i);
}

编辑,2013 年: 这些现在通常被称为IIFE

我不确定你是否理解投反对票的原因。除了安迪的(优秀)答案:IIFE 之外,这个答案确实添加了信息。
2021-03-12 21:20:47
这里没有,但是 -1 只是因为 Andy E 以更多的解释首先到达那里;这个答案不会向页面添加任何内容。
2021-03-16 21:20:47

你即将结束。这是一篇关于闭包以及如何使用它们的文章。查看页面上的示例 5;这就是你正在处理的场景。

编辑:四年后,该链接已失效。上述问题的根源在于for循环形成了闭包(特别是在 上marker = results[i])。marker传入 时addEventListener,您会看到闭包的副作用:共享的“环境”随着循环的每次迭代而更新,然后在最终迭代之后通过闭包最终“保存”之前。MDN 很好地解释了这一点。

for (var i = 0; i < results.length; i++) {
    marker = results[i];
    google.maps.event.addListener(marker, 'click', (function(i) {
        return function(){
            change_selection(i);
        }
    })(i)); 
}
如果您解释了它的工作原理,这将是一个更好的答案。
2021-03-30 21:20:47