当前位置:  开发笔记 > 编程语言 > 正文

循环内的JavaScript闭包 - 简单实用的例子

如何解决《循环内的JavaScript闭包-简单实用的例子》经验,为你挑选了23个好方法。

var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
  // and store them in funcs
  funcs[i] = function() {
    // each should log its value.
    console.log("My value: " + i);
  };
}
for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}

它输出这个:

我的价值:3
我的价值:3
我的价值:3

而我希望它输出:

我的价值:0
我的价值:1
我的价值:2


使用事件侦听器导致运行函数的延迟时,会出现同样的问题:

var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
  // as event listeners
  buttons[i].addEventListener("click", function() {
    // each should log its value.
    console.log("My value: " + i);
  });
}



...或异步代码,例如使用Promises:

// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));

for (var i = 0; i < 3; i++) {
  // Log `i` as soon as each promise resolves.
  wait(i * 100).then(() => console.log(i));
}

这个基本问题的解决方案是什么?



1> harto..:

好吧,问题是i每个匿名函数中的变量都绑定到函数外部的同一个变量.

经典解决方案:闭包

你想要做的是将每个函数中的变量绑定到函数之外的一个单独的,不变的值:

var funcs = [];

function createfunc(i) {
  return function() {
    console.log("My value: " + i);
  };
}

for (var i = 0; i < 3; i++) {
  funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}


2> Bjorn..:

尝试:

var funcs = [];
    
for (var i = 0; i < 3; i++) {
    funcs[i] = (function(index) {
        return function() {
            console.log("My value: " + index);
        };
    }(i));
}

for (var j = 0; j < 3; j++) {
    funcs[j]();
}

编辑(2014):

我个人认为@ Aust 最近关于使用的答案.bind是现在做这种事情的最好方法.还有LO-破折号/下划线的_.partial,当你不需要或不想要惹bindthisArg.


@aswzen我认为它将`i`作为参数`index`传递给函数.
关于`}(i));`的任何解释吗?

3> Aust..:

另一种尚未提及的方法是使用 Function.prototype.bind

var funcs = {};
for (var i = 0; i < 3; i++) {
  funcs[i] = function(x) {
    console.log('My value: ' + x);
  }.bind(this, i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();
}


4> neurosnap..:

使用立即调用的函数表达式,这是封装索引变量的最简单,最易读的方法:

for (var i = 0; i < 3; i++) {

    (function(index) {

        console.log('iterator: ' + index);
        //now you can also loop an ajax call here 
        //without losing track of the iterator value:   $.ajax({});
    
    })(i);

}

这会将迭代器发送i到我们定义为的匿名函数中index.这将创建一个闭包,i保存变量以供以后在IIFE中的任何异步功能中使用.


为了进一步提高代码的可读性并避免混淆`i`是什么,我将函数参数重命名为`index`.
您将如何使用此技术来定义原始问题中描述的数组*funcs*?

5> woojoo666..:

派对迟到了,但我今天正在探讨这个问题,并注意到许多答案并没有完全解决Javascript如何处理范围,这基本上归结为这个问题.

正如许多其他人提到的那样,问题是内部函数引用了相同的i变量.那么为什么我们不在每次迭代时只创建一个新的局部变量,而是使用内部函数引用呢?

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '

' + msg + '

';}; var funcs = {}; for (var i = 0; i < 3; i++) { var ilocal = i; //create a new local variable funcs[i] = function() { console.log("My value: " + ilocal); //each should reference its own local variable }; } for (var j = 0; j < 3; j++) { funcs[j](); }


6> Ben McCormic..:

随着ES6现在得到广泛支持,这个问题的最佳答案已经改变.ES6 为这种确切的情况提供了关键字letconst关键字.我们可以let像使用这样设置一个循环范围变量,而不是搞乱闭包.

var funcs = [];

for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}

val然后指向一个特定于循环特定转向的对象,并返回正确的值而不使用额外的闭包表示法.这显然简化了这个问题.

constlet初始赋值后变量名不能反弹到新引用的附加限制类似.

浏览器支持现在适用于针对最新版浏览器的用户.const/ let目前支持最新的Firefox,Safari,Edge和Chrome.Node也支持它,你可以利用像Babel这样的构建工具在任何地方使用它.你可以在这里看到一个有效的例子:http://jsfiddle.net/ben336/rbU4t/2/

文件在这里:

常量

但要注意,IE9-IE11和Edge在Edge 14支持之前let却出现了上述错误(它们i每次都没有创建新的,所以上面的所有函数都会像我们使用的那样记录3 var).Edge 14最终做对了.


截至2016年6月,除了iOS Safari,Opera Mini和Safari 9之外,所有主要浏览器版本均支持[let](http://caniuse.com/#search=let).常青浏览器支持它.Babel会正确地将其转换为保持预期行为而不打开高合规性模式.

7> Darren Clark..:

另一种说法是,i函数中的函数在执行函数时受到约束,而不是创建函数的时间.

创建闭包时,i是对外部作用域中定义的变量的引用,而不是创建闭包时的副本.它将在执行时进行评估.

大多数其他答案提供了通过创建另一个不会为您更改值的变量来解决的方法.

我想我会添加一个清晰的解释.对于一个解决方案,就个人而言,我会选择Harto,因为从这里的答案来看,这是最不言自明的方式.发布的任何代码都可以使用,但我选择封闭工厂而不必写一堆注释来解释为什么我要声明一个新变量(Freddy和1800's)或者有奇怪的嵌入式闭包语法(apphacker).



8> eglasius..:

你需要了解的是javascript中变量的范围是基于函数的.这是一个重要的区别,而不是c#,你有块范围,只是将变量复制到for内的一个将起作用.

将它包装在一个函数中,将函数评估为像apphacker的答案一样返回函数将完成这一操作,因为变量现在具有函数范围.

还有一个let关键字而不是var,允许使用块范围规则.在那种情况下,在for中定义变量就可以了.也就是说,由于兼容性,let关键字不是一个实用的解决方案.

var funcs = {};

for (var i = 0; i < 3; i++) {
  let index = i; //add this
  funcs[i] = function() {
    console.log("My value: " + index); //change to the copy
  };
}

for (var j = 0; j < 3; j++) {
  funcs[j]();
}


另请参见[当前哪些浏览器支持javascript的'let'关键字?](http://stackoverflow.com/questions/2356830/what-browsers-currently-support-javascripts-let-keyword)
@nickf嗯,实际上你必须明确指定版本:

现在按F12打开Chrome控制台窗口并刷新页面.在数组中扩展每3个函数.您将看到一个名为.Expand 的属性.您将看到一个被调用的数组对象,展开该对象.您将找到声明为对象的属性,其值为3.[[Scopes]]"Global"'i'

在此输入图像描述

在此输入图像描述

结论:

    当您'var'在函数外部声明变量时,它将变为全局变量(您可以通过键入iwindow.i在控制台窗口中进行检查.它将返回3).

    除非您调用函数,否则您声明的不可靠函数将不会调用并检查函数内的值.

    调用函数时,console.log("My value: " + i)从其Global对象获取值并显示结果.

CASE2:使用let

现在更换'var''let'


做同样的事情,转到范围.现在你将看到两个对象"Block""Global".现在展开Block对象,你会看到'i'在那里被定义,奇怪的是,对于每个函数,值if i是不同的(0,1,2).

在此输入图像描述

结论:

当你使用'let'函数外部但在循环内部声明变量时,这个变量将不是一个全局变量,它将成为一个Block只能用于同一个函数的级别变量.这就是我们得到i不同价值的原因.当我们调用函数时为每个函数.

有关近距离工作的更多细节,请浏览精彩的视频教程https://youtu.be/71AtaJpJHw0

推荐阅读
喜生-Da
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有