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

nickf 2689 javascript closures loops

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);
  });
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>

...或异步代码,例如使用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));
}

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

harto.. 2073

好吧,问题是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]();
}

由于JavaScript中没有块作用域 - 只有函数作用域 - 通过将函数创建包装在新函数中,可以确保"i"的值保持不变.


2015解决方案:forEach

随着Array.prototype.forEach函数的相对广泛的可用性(在2015年),值得注意的是,在涉及主要在一组值上进行迭代的那些情况下,.forEach()提供了一种干净,自然的方式来为每次迭代获得明显的闭包.也就是说,假设您有某种包含值的数组(DOM引用,对象等等),并且设置了特定于每个元素的回调问题,您可以这样做:

var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
  // ... code code code for this one element
  someAsynchronousFunction(arrayElement, function() {
    arrayElement.doSomething();
  });
});

这个想法是每次调用与.forEach循环一起使用的回调函数都是它自己的闭包.传递给该处理程序的参数是特定于该迭代的特定步骤的数组元素.如果它在异步回调中使用,它将不会与在迭代的其他步骤中建立的任何其他回调冲突.

如果你碰巧在jQuery中工作,该$.each()函数会为你提供类似的功能.


ES6解决方案: let

ECMAScript 6(ES6)引入了新的letconst关键字,其范围与var基于变量的不同.例如,在具有let基于索引的循环中,循环中的每次迭代都将具有一个新值,i其中每个值都在循环中作用域,因此您的代码将按预期工作.有很多资源,但我建议将2ality的区块范围发布作为一个很好的信息来源.

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

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

  • @Wladimir:你的建议是`.bind()`是*"正确答案"*是不对的.他们每个人都有自己的位置.使用`.bind()`你不能绑定参数而不绑定`this`值.你也得到了一个`i`参数的副本,但没有能力在调用之间进行变异,这有时是需要的.所以它们是完全不同的结构,更不用说`.bind()`实现在历史上一直很慢.当然,在简单的例子中,任何一个都可以工作,但是闭包是一个需要理解的重要概念,这就是问题所在. (75认同)
  • 不幸的是,这个答案已经过时了,没有人会在底部看到正确的答案 - 现在使用`Function.bind()`绝对是可取的,请参阅/sf/ask/17360801/. (53认同)
  • @ChristianLandgren:只有在迭代一个数组时才有用.这些技术不是"黑客".他们是必不可少的知识. (24认同)
  • 请停止使用这些for-return功能黑客,使用[] .forEach或[] .map代替,因为它们避免重复使用相同的范围变量. (8认同)
  • 不是`function createfunc(i){return function(){console.log("我的值:"+ i); }; }`仍然关闭,因为它使用变量`i`? (7认同)
  • 注意在`for`循环的`()`中使用`let`在IE中不能正常工作(一种解决方法:在`for`的`{}`块中添加一个`let`变量,**在IE中正常工作). (3认同)
  • 这是一个很好的解释,我一直在阅读闭包,但不清楚地了解i值不是1,2,3唯一的。但是阅读完您的文章后,我了解到javascript没有块范围,而只有函数范围。感谢您的简单答案。 (2认同)

Bjorn.. 370

尝试:

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`传递给函数. (3认同)
  • 关于`}(i));`的任何解释吗? (2认同)

Aust.. 337

另一种尚未提及的方法是使用 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]();
}

UPDATE

正如@squint和@mekdev所指出的那样,通过首先在循环外创建函数然后在循环中绑定结果,可以获得更好的性能.

function log(x) {
  console.log('My value: ' + x);
}

var funcs = [];

for (var i = 0; i < 3; i++) {
  funcs[i] = log.bind(this, i);
}

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

  • 使用ECMAScript 6功能,`.bind()`将基本上过时.此外,这实际上每次迭代创建两个函数.首先是匿名,然后是`.bind()`生成的.更好的用法是在循环外创建它,然后``.bind()`在里面. (17认同)
  • @squint @mekdev - 你们两个都是对的.我最初的例子是为了演示如何使用`bind`来快速编写的.我根据你的建议添加了另一个例子. (5认同)
  • 我认为不是在两个O(n)循环上浪费计算,而是为(var i = 0; i <3; i ++){log.call(this,i); } (5认同)

neurosnap.. 262

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

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`. (9认同)
  • 您将如何使用此技术来定义原始问题中描述的数组*funcs*? (5认同)

woojoo666.. 155

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

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

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

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]();
}

就像之前一样,每个内部函数输出分配给的最后一个值i,现在每个内部函数只输出分配给的最后一个值ilocal.但是,每次迭代都不应该拥有它ilocal吗?

事实证明,这就是问题所在.每次迭代都共享相同的范围,因此在第一次迭代之后的每次迭代都只是覆盖ilocal.来自MDN:

重要提示:JavaScript没有块范围.使用块引入的变量的范围限定为包含函数或脚本,并且设置它们的效果将持续超出块本身.换句话说,块语句不引入范围.虽然"独立"块是有效的语法,但您不希望在JavaScript中使用独立块,因为如果您认为它们在C或Java中执行类似块的操作,则它们不会按照您的想法执行操作.

重申强调:

JavaScript没有块范围.使用块引入的变量的范围限定为包含函数或脚本

我们可以ilocal在每次迭代中声明它之前通过检查来看到这一点:

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

var funcs = {};
for (var i = 0; i < 3; i++) {
  console.log(ilocal);
  var ilocal = i;
}

这正是这个bug如此棘手的原因.即使您重新声明变量,Javascript也不会抛出错误,JSLint甚至不会发出警告.这也是为什么解决这个问题的最好方法是利用闭包,这本质上是在Javascript中,内部函数可以访问外部变量,因为内部作用域"包围"外部作用域.

关闭

这也意味着内部函数"保持"外部变量并使它们保持活动,即使外部函数返回.为了利用这一点,我们创建并调用一个包装器函数,纯粹是为了创建一个新的作用域,ilocal在新作用域中声明,并返回一个使用的内部函数ilocal(下面有更多解释):

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

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = (function() { //create a new scope using a wrapper function
        var ilocal = i; //capture i into a local var
        return function() { //return the inner function
            console.log("My value: " + ilocal);
        };
    })(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

在包装器函数中创建内部函数为内部函数提供了一个只有它可以访问的私有环境,即"闭包".因此,每次调用包装器函数时,我们都会创建一个新的内部函数,它具有自己独立的环境,确保ilocal变量不会相互冲突和相互覆盖.一些小的优化给出了许多其他SO用户给出的最终答案:

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

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
    return function() { //return the inner function
        console.log("My value: " + ilocal);
    };
}

更新

现在ES6已成为主流,我们现在可以使用new let关键字创建块范围变量:

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

var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
    funcs[i] = function() {
        console.log("My value: " + i); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
    funcs[j]();
}

看看它现在有多容易!有关详细信息,请参阅此答案,我的信息基于此答案.

  • 现在使用`let`和`const`关键字在JavaScript中进行块作用域.如果这个答案扩大到包括那个,那么在我看来它将更具全球性. (3认同)

Ben McCormic.. 146

随着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会正确地将其转换为保持预期行为而不打开高合规性模式. (2认同)

Darren Clark.. 85

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

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

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

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


eglasius.. 70

你需要了解的是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) (4认同)
  • @nickf嗯,实际上你必须明确指定版本:<script type ="application/javascript; version = 1.7"/> ...由于IE的限制,我实际上没有在任何地方使用它,它只是不是实际的 :( (2认同)

Boann.. 58

这是该技术的另一种变体,类似于Bjorn(apphacker),它允许您在函数内部分配变量值,而不是将其作为参数传递,有时可能更清晰:

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

请注意,无论使用何种技术,index变量都会变成一种静态变量,绑定到内部函数的返回副本.即,在调用之间保留对其值的更改.它可以非常方便.


Mave.. 52

这描述了在JavaScript中使用闭包的常见错误.

函数定义新环境

考虑:

function makeCounter()
{
  var obj = {counter: 0};
  return {
    inc: function(){obj.counter ++;},
    get: function(){return obj.counter;}
  };
}

counter1 = makeCounter();
counter2 = makeCounter();

counter1.inc();

alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0

对于每次makeCounter调用,都会{counter: 0}导致创建一个新对象.此外,obj 还会创建新副本以引用新对象.因此,counter1counter2相互独立的.

循环中的闭包

在循环中使用闭包很棘手.

考虑:

var counters = [];

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = {
      inc: function(){obj.counter++;},
      get: function(){return obj.counter;}
    }; 
  }
}

makeCounters(2);

counters[0].inc();

alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1

请注意,counters[0]counters[1]不是独立的.事实上,他们的运作方式相同obj!

这是因为obj在循环的所有迭代中只有一个共享副本,可能是出于性能原因.即使{counter: 0}在每次迭代中创建一个新对象,相同的副本obj也只会通过对最新对象的引用进行更新.

解决方案是使用另一个辅助函数:

function makeHelper(obj)
{
  return {
    inc: function(){obj.counter++;},
    get: function(){return obj.counter;}
  }; 
}

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = makeHelper(obj);
  }
}

这是有效的,因为函数作用域中的局部变量以及函数参数变量在进入时都会分配新的副本.

有关详细讨论,请参阅JavaScript闭包陷阱和用法


Kemal Dağ.. 48

最简单的解决方案是,

而不是使用:

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

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

提醒"2",共3次.这是因为在for循环中创建的匿名函数共享相同的闭包,并且在该闭包中,值i是相同的.使用它来防止共享关闭:

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

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

这背后的想法是,使用IIFE(立即调用的函数表达式)封装for循环的整个主体,并new_i作为参数传递并将其捕获为i.由于匿名函数是立即执行的,i因此匿名函数内定义的每个函数的值都不同.

这个解决方案似乎适合任何这样的问题,因为它需要对遇到此问题的原始代码进行最小的更改.事实上,这是设计,它应该不是一个问题!

  • 自我调用或自我调用不适合这种技术,**IIFE**(立即调用函数表达式)更准确.参考:http://benalman.com/news/2010/11/immediately-invoked-function-expression/ (3认同)
  • 一次读一本类似的东西.我也更喜欢这个,因为你不必触及你现有的代码(同样多),一旦你学会了自我调用函数模式,你就会明白为什么这样做:在新创建的函数中捕获该变量范围. (2认同)

小智.. 30

试试这个较短的一个

  • 没有数组

  • 没有额外的循环


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

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

http://jsfiddle.net/7P6EN/


Daryl.. 28

这是一个使用的简单解决方案forEach(回到IE9):

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

打印:

My value: 0
My value: 1
My value: 2


Travis J.. 26

OP显示的代码的主要问题i是在第二个循环之前永远不会读取.为了演示,想象一下在代码中看到错误

funcs[i] = function() {            // and store them in funcs
    throw new Error("test");
    console.log("My value: " + i); // each should log its value.
};

funcs[someIndex]执行之前实际上不会发生错误().使用相同的逻辑,显然在i此之前也不会收集值.一旦原始循环结束,i++i导致其值3导致条件i < 3失败并且循环结束.在这一点上,i3等时funcs[someIndex]()使用,并i进行评估,这是3 -每一次.

为了解决这个问题,您必须i在遇到问题时进行评估.请注意,这已经以funcs[i](有3个唯一索引)的形式发生.有几种方法可以捕获此值.一种是将其作为参数传递给函数,该函数已经以几种方式显示在此处.

另一个选择是构造一个能够关闭变量的函数对象.这可以这样完成

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};


Costa.. 22

JavaScript函数"关闭"它们在声明时可以访问的范围,并保留对该范围的访问权限,即使该范围中的变量发生更改.

var funcs = []

for (var i = 0; i < 3; i += 1) {
  funcs[i] = function () {
    console.log(i)
  }
}

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

上面数组中的每个函数都关闭全局范围(全局,只是因为它恰好是它们声明的范围).

稍后调用这些函数记录i全局范围中的最新值.这是封闭的魔力和挫败感.

"JavaScript函数关闭它们声明的范围,并保留对该范围的访问权限,即使该范围内的变量值发生变化."

使用let而不是var通过在每次for循环运行时创建新范围来解决此问题,为每个要关闭的函数创建一个单独的范围.各种其他技术通过额外功能执行相同的操作.

var funcs = []

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

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

(使用let块作用域而不是函数作用域的变量.块用大括号表示,但在for循环的i情况下,初始化变量,在我们的例子中,被认为是在大括号中声明.)


小智.. 13

在阅读了各种解决方案之后,我想补充一点,这些解决方案的工作原理是依赖于范围链的概念.这是JavaScript在执行期间解析变量的方式.

  • 每个函数定义形成一个范围,包括var由其声明的所有局部变量arguments.
  • 如果我们在另一个(外部)函数中定义了内部函数,则会形成一个链,并将在执行期间使用
  • 执行函数时,运行时通过搜索范围链来评估变量.如果可以在链的某个点找到变量,它将停止搜索并使用它,否则它将一直持续到达到属于的全局范围window.

在初始代码中:

funcs = {};
for (var i = 0; i < 3; i++) {         
  funcs[i] = function inner() {        // function inner's scope contains nothing
    console.log("My value: " + i);    
  };
}
console.log(window.i)                  // test value 'i', print 3

funcs执行时,范围链将是function inner -> global.由于i无法找到变量function inner(既没有声明使用var也没有作为参数传递),它继续搜索,直到i最终在全局范围中找到值window.i.

通过将它包装在外部函数中,可以显式定义像harto那样的辅助函数,也可以像Bjorn那样使用匿名函数:

funcs = {};
function outer(i) {              // function outer's scope contains 'i'
  return function inner() {      // function inner, closure created
   console.log("My value: " + i);
  };
}
for (var i = 0; i < 3; i++) {
  funcs[i] = outer(i);
}
console.log(window.i)          // print 3 still

funcs执行时,现在范围链将是function inner -> function outer.这个时间i可以在外部函数的范围中找到,该范围在for循环中执行3次,每次都i正确绑定值.它不会使用window.i内部执行时的值.

更多细节可以在这里找到
它包括我们在这里创建闭包的常见错误,以及为什么我们需要关闭和性能考虑.


Prithvi Uppa.. 13

通过ES6的新功能,可以管理块级别范围:

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

OP问题中的代码替换letvar.


Christian La.. 10

我很惊讶没有人建议使用该forEach函数来更好地避免(重新)使用局部变量.事实上,for(var i ...)由于这个原因,我根本就不再使用了.

[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3

//编辑使用forEach而不是地图.

  • 如果你没有真正映射任何东西,`.forEach()`是一个更好的选择,而Daryl建议你在发布前7个月,所以没什么可惊讶的. (3认同)

sidhuko.. 9

这个问题真的展示了JavaScript的历史!现在我们可以避免使用箭头函数进行块作用域,并使用Object方法直接从DOM节点处理循环.

const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())

const buttons = document.getElementsByTagName("button");
Object
  .keys(buttons)
  .map(i => buttons[i].addEventListener('click', () => console.log(i)));
<button>0</button><br>
<button>1</button><br>
<button>2</button>


jottos.. 8

原始示例不起作用的原因是您在循环中创建的所有闭包都引用了相同的帧.实际上,在一个对象上只有一个i变量有3个方法.他们都打印出相同的价值.


Ali Kahoot.. 8

首先,了解这段代码的错误:

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

这里当funcs[]数组被初始化时,i正在递增,funcs数组被初始化并且func数组的大小变为3,所以i = 3,.现在,当funcs[j]()调用它时,它再次使用变量i,该变量已经增加到3.

现在要解决这个问题,我们有很多选择.以下是其中两个:

  1. 我们可以初始化ilet或初始化一个新的变量indexlet和使其等于i.因此,当进行调用时,index将使用它并且其范围将在初始化之后结束.对于呼叫,index将再次初始化:

    var funcs = [];
    for (var i = 0; i < 3; i++) {          
        let index = i;
        funcs[i] = function() {            
            console.log("My value: " + index); 
        };
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
    
  2. 其他选项可以引入一个tempFunc返回实际函数:

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


Vikash Singh.. 7

使用闭包结构,这将减少你的额外for循环.你可以在一个for循环中完成它:

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


Bimal Das.. 7

我们将检查,当您声明varlet 逐个实际发生时.

案例1:使用var

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

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

在此输入图像描述

在此输入图像描述

结论:

  1. 当您'var'在函数外部声明变量时,它将变为全局变量(您可以通过键入iwindow.i在控制台窗口中进行检查.它将返回3).
  2. 除非您调用函数,否则您声明的不可靠函数将不会调用并检查函数内的值.
  3. 调用函数时,console.log("My value: " + i)从其Global对象获取值并显示结果.

CASE2:使用let

现在更换'var''let'

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

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

在此输入图像描述

结论:

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

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


归档时间:

查看次数:

335383 次

最近记录:

10 月,3 周 前