循环内的JavaScript闭合–简单的实际示例

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

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


#1楼

这是该技术的另一种变体,类似于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变量都将成为一种静态变量,绑定到内部函数的返回副本上。 即,在两次调用之间保留对其值的更改。 可能非常方便。


#2楼

这描述了在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创建一个新的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封闭陷阱和用法


#3楼

如今,ES6得到了广泛支持,对此问题的最佳答案已经改变。 ES6为这种确切情况提供了letconst关键字。 不用弄乱闭包,我们可以使用let来设置这样的循环作用域变量:

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

val然后将指向特定于该循环特定循环的对象,并将返回正确的值,而无需附加的闭合符号。 这显然大大简化了这个问题。

constlet类似,但有一个额外的限制,即变量名称在初始赋值后不能反弹到新引用。

现在,针对那些针对最新版本浏览器的浏览器提供了支持。 最新的Firefox,Safari,Edge和Chrome当前支持const / let 。 它在Node中也受支持,您可以利用Babel等构建工具在任何地方使用它。 您可以在这里看到一个有效的示例: http : //jsfiddle.net/ben336/rbU4t/2/

此处的文档:

但是请注意,IE9-IE11和Edge 14之前的Edge支持let上面的错误出现了(它们不会每次都创建一个新的i ,因此上面的所有函数都将记录3,就像我们使用var )。 Edge 14终于正确了。


#4楼

最简单的解决方案是

而不是使用:

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值都不同。

此解决方案似乎适合任何此类问题,因为它将需要对遭受此问题的原始代码进行最少的更改。 实际上,这是设计使然,根本不成问题!


#5楼

试试这个较短的

  • 没有数组

  • 没有额外的循环


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

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

http://jsfiddle.net/7P6EN/


#6楼

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

更新

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


#7楼

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

 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中的任何异步功能中使用。


#8楼

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

#9楼

这是一个使用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 

#10楼

在阅读了各种解决方案之后,我想补充一点,那些解决方案起作用的原因是依赖于范围链的概念。 这是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 。 由于无法在function inner找到变量i (既不使用var声明也不作为参数传递),因此它将继续搜索,直到最终在window.i全局范围中找到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的值。

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


#11楼

我感到惊讶的是,没有人建议使用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而不是map。


#12楼

参加聚会的时间有点晚,但是我今天正在探讨这个问题,并注意到许多答案并没有完全解决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没有阻止范围。 随块引入的变量的作用域为包含的函数或脚本,并且设置它们的效果在块本身之外仍然存在。 换句话说,block语句不会引入作用域。 尽管“独立”块是有效的语法,但是您不希望在JavaScript中使用独立块,因为如果您认为独立块在C或Java中的作用类似于此类块,则它们不会像您想的那样工作。

重申重点:

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

在包装函数内部创建内部函数会为内部函数提供一个只有其才能访问的私有环境,即“闭包”。 因此,每次调用wrapper函数时,我们都会使用其自己的独立环境创建一个新的内部函数,以确保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现在成为主流,我们现在可以使用新的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](); } 

看看现在有多容易! 有关更多信息,请参见此答案 ,这是我的信息所基于的。


#13楼

您可以对数据列表使用声明性模块,例如query-js (*)。 在这些情况下,我个人发现声明式方法不足为奇

var funcs = Query.range(0,3).each(function(i){
     return  function() {
        console.log("My value: " + i);
    };
});

然后,您可以使用第二个循环并获得预期的结果,或者您可以执行

funcs.iterate(function(f){ f(); });

(*)我是query-js的作者,因此偏向于使用它,因此不要只将我的话作为对上述库的建议:)


#14楼

我更喜欢使用forEach函数,该函数在创建伪范围时有其自己的关闭方式:

var funcs = [];

new Array(3).fill(0).forEach(function (_, i) { // creating a range
    funcs[i] = function() {            
        // now i is safely incapsulated 
        console.log("My value: " + i);
    };
});

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

这看起来比其他语言的范围丑陋,但是恕我直言,它不如其他解决方案那么可怕。


#15楼

还有另一种解决方案:无需创建另一个循环,只需将this绑定到return函数。

 var funcs = []; function createFunc(i) { return function() { console.log('My value: ' + i); //log value of i. }.call(this); } for (var i = 1; i <= 5; i++) { //5 functions funcs[i] = createFunc(i); // call createFunc() i=5 times } 

通过绑定 ,也解决了问题。


#16楼

首先,了解此代码有什么问题:

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 现在,当调用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](); } 

#17楼

您的代码无效,因为它的作用是:

Create variable `funcs` and assign it an empty array;  
Loop from 0 up until it is less than 3 and assign it to variable `i`;
    Push to variable `funcs` next function:  
        // Only push (save), but don't execute
        **Write to console current value of variable `i`;**

// First loop has ended, i = 3;

Loop from 0 up until it is less than 3 and assign it to variable `j`;
    Call `j`-th function from variable `funcs`:  
        **Write to console current value of variable `i`;**  
        // Ask yourself NOW! What is the value of i?

现在的问题是,调用函数时变量i的值是多少? 因为第一个循环是在条件i < 3的情况下创建的,所以当条件为false时,它将立即停止,所以它是i = 3

您需要了解,在创建函数时,不会执行任何代码,只会将其保存以供以后使用。 因此,当稍后调用它们时,解释器将执行它们并询问:“ i的当前值是多少?”

因此,您的目标是首先将i的值保存到函数,然后funcs函数保存到funcs 。 例如,可以通过以下方式完成此操作:

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

这样,每个函数将具有自己的变量x ,我们在每次迭代中将此x设置为i的值。

这只是解决此问题的多种方法之一。


#18楼

借助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问题中的代码被let代替var代替。


#19楼

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 make变量成为块作用域。块用花括号表示,但是在for循环的情况下,初始化变量(在本例中为i被视为在花括号中声明。)


#20楼

使用闭包结构,这样可以减少多余的for循环。 您可以在单个for循环中执行此操作:

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

#21楼

许多解决方案似乎都是正确的,但他们没有提及它称为Currying ,这是一种针对Currying情况的功能编程设计模式。 比绑定速度快3到10倍,具体取决于浏览器。

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

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

查看不同浏览器中的性能提升


#22楼

这个问题确实显示了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> 


#23楼

我们将检查,当您声明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个函数。您将看到一个名为[[Scopes]]属性。 您将看到一个名为"Global"数组对象,将其展开。 您会发现在对象中声明的属性'i'值为3。

在此处输入图片说明

在此处输入图片说明

结论:

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

案例2:使用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”,而奇怪的是,对于每个函数,如果i不同(0、1、2),则该值为。

在此处输入图片说明

结论:

当您甚至在函数外部但在循环内部使用'let'声明变量时,此变量将不是全局变量,它将变为Block级变量,仅适用于同一函数。这就是我们当我们调用函数时,每个函数的i值都不同。

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


#24楼

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

#25楼

使用let(blocked-scope)代替var。

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


#26楼

尝试:

 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最新解答是现在执行此类操作的最佳方法。 当您不需要或不喜欢bind_.partial时,还会有破折号/下划线的thisArg


#27楼

好吧,问题在于每个匿名函数中的变量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 14之前的Edge支持let上面的错误出现了(它们不会每次都创建一个新的i ,因此上面的所有函数都将记录3,就像我们使用var )。 Edge 14终于正确了。


#28楼

您的原始示例无效的原因是您在循环中创建的所有闭包都引用了同一框架。 实际上,只有一个i变量在一个对象上具有3种方法。 它们都打印出相同的值。


#29楼

您需要了解的是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](); } 


#30楼

换句话说,函数中的i是在执行函数时绑定的,而不是在创建函数时绑定的。

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

其他大多数答案都提供了解决方法,方法是创建另一个不会改变您价值的变量。

只是以为我会添加一个解释以便清楚。 对于解决方案,就我个人而言,我会选择Harto,因为这是从此处的答案中最不言自明的方式。 发布的任何代码都可以使用,但是我不得不选择一个关闭工厂,而不必编写一堆注释来解释为什么我要声明一个新变量(Freddy和1800's)或具有怪异的嵌入式关闭语法(apphacker)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值