Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/loops/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
循环内的JavaScript闭包–简单实用的示例_Javascript_Loops_Closures - Fatal编程技术网

循环内的JavaScript闭包–简单实用的示例

循环内的JavaScript闭包–简单实用的示例,javascript,loops,closures,Javascript,Loops,Closures,var funcs=[]; //让我们创建3个函数 对于var i=0;i

var funcs=[]; //让我们创建3个函数 对于var i=0;i<3;i++{ //并将其存储在funcs中 funcs[i]=函数{ //每个都应记录其值。 console.logMy值:+i; }; } 对于var j=0;j<3;j++{ //现在让我们每个人都来看看 funcs[j]; } 尝试:

var funcs=[]; 对于var i=0;i<3;i++{ funcs[i]=函数索引{ 返回函数{ console.logMy值:+index; }; }一,; } 对于var j=0;j<3;j++{ funcs[j];
} 问题是,每个匿名函数中的变量i被绑定到函数外的同一个变量

ES6解决方案:让 ECMAScript 6 ES6引入了新的let和const关键字,它们的作用域不同于基于var的变量。例如,在具有基于let的索引的循环中,循环中的每个迭代都将有一个具有循环范围的新变量i,因此您的代码将按预期工作。有很多资源,但我推荐它作为一个很好的信息来源

for (let i = 0; i < 3; i++) {
  funcs[i] = function() {
    console.log("My value: " + i);
  };
}
其思想是,与.forEach循环一起使用的回调函数的每次调用都将是它自己的闭包。传递给该处理程序的参数是特定于迭代特定步骤的数组元素。如果在异步回调中使用,它将不会与在迭代的其他步骤中建立的任何其他回调冲突

如果您碰巧使用jQuery,则$。每个函数都提供类似的功能

经典解决方案:闭包 您要做的是将每个函数内的变量绑定到函数外的一个单独的、不变的值:

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);
  }
}
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]();                        
}
var funcs=[]; 函数createfunci{ 返回函数{ console.logMy值:+i; }; } 对于var i=0;i<3;i++{ funcs[i]=createfunci; } 对于var j=0;j<3;j++{ //现在让我们每个人都来看看 funcs[j];
} 您最初的示例不起作用的原因是,您在循环中创建的所有闭包都引用了相同的框架。实际上,在一个对象上有3个方法,只有一个i变量。它们都输出了相同的值。

您需要了解的是javascript中变量的范围是基于函数的。这与使用块作用域的c相比是一个重要的区别,只需将变量复制到for中的一个变量就可以了

将其包装在一个函数中,该函数将像apphacker的答案那样返回函数,因为变量现在具有函数作用域

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

var funcs={}; 对于var i=0;i<3;i++{ 让index=i;//添加这个 funcs[i]=函数{ console.logMy值:+index;//更改为副本 }; } 对于var j=0;j<3;j++{ funcs[j];
} 另一种说法是,函数中的i是在执行函数时绑定的,而不是在创建函数时绑定的

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

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


我只是想加一个解释来澄清。对于一个解决方案,个人来说,我会选择哈托的方法,因为这是从这里的答案中最不言自明的方法。发布的任何代码都可以工作,但我会选择闭包工厂,而不是写一堆注释来解释为什么我要声明一个新变量Freddy和1800,或者有一个奇怪的嵌入式闭包syntaxapphacker。

这是该技术的另一个变体,类似于Bjorn的apphacker,这使您可以在函数中指定变量值,而不是将其作为参数传递,这有时可能更清楚:

var funcs=[]; 对于var i=0;i<3;i++{ funcs[i]=函数{ var指数=i; 返回函数{ console.logMy值:+index; } };
} 这描述了在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副本 也将创建以引用新对象。因此,计数器1和计数器2相互独立

循环中的闭包 在循环中使用闭包是很棘手的

考虑:

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
这是因为函数作用域中的局部变量以及函数参数变量都是直接分配的
新的输入副本。

随着ES6现在得到广泛支持,这个问题的最佳答案已经改变。ES6提供let和c onst关键字用于此特定情况。我们可以使用let设置一个循环范围变量,如下所示:

var funcs=[]; 对于let i=0;i<3;i++{ funcs[i]=函数{ console.logMy值:+i; };
} 最简单的解决办法是

而不是使用:

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

for(var j =0; j<3; j++){
    funcs[j]();
}
这背后的思想是,用一个立即调用的函数表达式封装for循环的整个主体,并将新的_i作为参数传递,并将其作为i捕获。由于匿名函数立即执行,因此在匿名函数中定义的每个函数的i值都不同

此解决方案似乎适合任何此类问题,因为它需要对受此问题影响的原始代码进行最小的更改。事实上,这是故意的,根本不应该成为问题

试试这个短一点的 无阵列

循环无额外费用


另一种尚未提及的方法是使用

var funcs={}; 对于var i=0;i<3;i++{ funcs[i]=functionx{ console.log'My value:'+x; }.这个,我; } 对于var j=0;j<3;j++{ funcs[j]; } 使用最简单、可读性最强的方法来封装索引变量:

对于var i=0;i<3;i++{ 函数索引{ log'iterator:'+索引; //现在您还可以在这里循环ajax调用 //在不丢失迭代器值的情况下:$.ajax{}; }一,;
} OP显示的代码的主要问题是,直到第二个循环,我才被读取。为了演示,想象一下看到代码内部的错误

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失败,循环结束。在这一点上,i是3,所以当使用funcs[someIndex]并计算i时,每次都是3

为了克服这个问题,您必须在遇到i时对其进行评估。请注意,这已经以funcs[i]的形式出现,其中有3个唯一索引。有几种方法可以获取此值。一种是将其作为参数传递给函数,这里已经以多种方式显示了该函数

另一种选择是构造一个函数对象,它将能够关闭变量。这是可以做到的


下面是一个使用forEach的简单解决方案,可追溯到IE9:

var funcs=[]; [0,1,2].forEachfunctioni{//让我们创建3个函数 funcs[i]=函数{//并将它们存储在funcs中 console.logMy value:+i;//每个都应该记录其值。 }; } 对于var j=0;j<3;j++{ funcs[j];//现在让我们运行每一个
} 在阅读了各种解决方案之后,我想补充一点,这些解决方案之所以有效,是因为它们依赖于范围链的概念。这是JavaScript在执行期间解析变量的方式

每个函数定义形成一个包含所有局部函数的范围 由var及其参数声明的变量。 如果我们在另一个外部函数中定义了内部函数,那么 形成链,并将在执行期间使用 执行函数时,运行时通过搜索范围链来计算变量。如果可以在链的某个点找到变量,它将停止搜索并使用它,否则它将继续,直到到达属于窗口的全局范围。 在初始代码中:

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被执行时,现在作用域链将是函数内部->函数外部。这次我可以在外部函数的作用域中找到,它在for循环中执行了3次,每次都正确绑定了值。当内部执行时,它不会使用window.i的值

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

我很惊讶还没有人建议使用forEach函数来更好地避免重用局部变量。事实上,我没有使用forvar I。。。因为这个原因,我再也不会这样做了

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

//编辑为使用forEach而不是map。

派对迟到了一点,但我今天探讨了这个问题,注意到许多答案并没有完全解决Javascript如何处理作用域的问题,这基本上就是问题的症结所在

正如许多其他人提到的,问题是内部函数引用的是同一个i变量。那么,我们为什么不在每次迭代中创建一个新的局部变量,并使用内部函数引用它呢

//覆盖console.log,以便查看控制台输出 console.log=functionmsg{document.body.innerHTML+=''+msg+'

';}; var funcs={}; 对于var i=0;i<3;i++{ var ilocal=i;//创建一个新的局部变量 芬克斯

如果使用数值索引,确定不希望funcs成为数组?只是提醒一下。这真是个令人困惑的问题。这也许它也能帮助其他人。另一个简单而明确的解决方案:1;2.闭包是一个可以访问父作用域的函数,即使在父函数关闭之后也是如此。请参阅此链接以了解ES6中更好的不可解析性,一个简单的解决方案是使用声明变量i,它的作用域是循环体。请阅读一本书中类似的内容。我也更喜欢这样,因为您不必过多地接触现有的代码,而且一旦您学会了自调用函数模式,就可以清楚地知道为什么要这样做:在新创建的作用域中捕获该变量。@DanMan谢谢。自调用匿名函数是处理javascript缺少块级变量作用域的非常好的方法。自调用或自调用不是这种技术的合适术语,IIFE立即调用函数表达式更准确。谢谢,你的解决方案很有效。但是我想问为什么这样做有效,但是交换var行和返回行行不通?谢谢@midnite如果您交换了var并返回,那么在返回内部函数之前不会分配变量。为了进一步提高代码可读性并避免混淆i是什么,我将函数参数重命名为index。您将如何使用此技术定义原始问题中描述的数组funcs?@Nico与原始问题中所示的方法相同,只是您将使用index而不是I。@JLRishe var funcs={};对于var i=0;i<3;i++{funcs[i]=functionindex{返回函数{console.log'iterator:'+index;};}i;};对于var j=0;j<3;j++{funcs[j];}@Nico在OP的特殊情况下,它们只是对数字进行迭代,所以这对于.forEach来说不是一个很好的例子,但是很多时候,当一个人从数组开始时,forEach是一个很好的选择,比如:var nums[4,6,7];var funcs={};nums.forEachfunction num,i{funcs[i]=函数{console.lognum;};};函数createfunci{return function{console.logMy value:+i;};}是否仍然是闭包,因为它使用了变量i?不幸的是,这个答案已经过时,没有人会在底部看到正确的答案-使用function.bind现在肯定更可取,请看。@Wladimir:你的建议是.bind是正确的答案,这是不对的。他们每个人都有自己的位置。使用.bind,如果不绑定this值,则无法绑定参数。此外,您还可以获得i参数的副本,但不能在调用之间对其进行变异,这有时是必需的。所以它们是完全不同的结构,更不用说。绑定实现在历史上一直很慢。当然,在这个简单的例子中,这两种方法都是可行的,但是闭包是一个需要理解的重要概念,这就是问题所在。请停止使用这些方法来攻击返回函数,改用[].forEach或[].map,因为它们避免重复使用相同的作用域变量。@ChristianLandgren:只有在迭代数组时才有用。这些技术不是黑客。它们是必要的知识。这也是我最近所做的,我也喜欢lo-dash/下划线的uz.partial.bind将在ECMAScript 6功能中基本过时。此外,这实际上会在每次迭代中创建两个函数。首先是匿名的,然后是由.bind生成的。更好的做法是在循环外部创建它。将它绑定到内部。@squint@mekdev-你们都是对的。我的初始示例是快速编写的,以演示如何使用bind。根据您的建议,我添加了另一个示例。我认为,与其将计算浪费在两个循环上,不如对var I=0进行计算;i<3;i++{log.callthis,i;}.bind做公认答案所建议的事情,再加上摆弄这个..如果你没有实际映射任何东西,forEach是一个更好的选择,Daryl建议在你发布之前7个月,所以没有什么值得惊讶的。这个问题不是关于数组上的循环,他想创建一个函数数组,这个例子展示了如何在不涉及全局变量的情况下创建一个数组。代码解决了手头的问题。了解如何潜在地改进代码是很有价值的。什么是查询。范围0,3?这不是此问题标签的一部分。此外,如果您使用第三方库,您可以提供文档链接。@jherax这些都是明显的改进。谢谢你的评论。我可以发誓已经有了联系。我想这篇文章是毫无意义的。我最初的想法是把它排除在外,因为我并没有试图推动使用我自己的库,而是更多的是声明性的想法。然而,在hinsight中,我完全同意链接应该在那里。您的解决方案似乎输出正确,但它不必要地使用函数,为什么不只是console.log输出?最初的问题是关于创建匿名函数

这是同样的结局。问题是,因为它们只有一个闭包,所以每个闭包的i值都是相同的。我希望你得到了。喜欢它胜过什么?这似乎是对其他答案的回应。它根本没有解决实际的问题,因为你没有分配一个函数,以后在任何地方调用。它与提到的问题完全相关:如何安全地迭代而不出现闭包问题。它似乎与公认的答案没有明显的不同。否。在接受的答案中,建议使用一些数组,但我们在答案中处理一个范围,这是完全不同的事情,不幸的是在js中没有一个好的解决方案,所以我的答案是尝试以良好的方式解决这个问题way@Quentin我建议在意外发生之前调查解决方案,“let”仍然没有得到充分支持,尤其是在移动设备中。从2016年6月起,除了iOS Safari、Opera Mini和Safari 9之外,所有主要浏览器版本都支持。常青浏览器支持它。Babel将正确地传输它,以保持预期的行为,而不打开高合规模式。@DanPantry是的,是时候更新了:更新以更好地反映事物的当前状态,包括添加对const的提及,文档链接和更好的兼容性信息。这难道不是我们使用babel来传输代码的原因吗,这样不支持ES6/7的浏览器就可以理解正在发生的事情了吗?在阅读这个答案之前,我一直在努力理解这个概念。它涉及到一个非常重要的点——i的值被设置为全局范围。当for循环完成运行时,i的全局值现在是3。因此,无论何时在数组中使用funcs[j]调用该函数,该函数中的i都会引用全局i变量,即3。我们很少实际编写此代码示例,但我认为它提供了一个很好的示例,可以帮助理解基本原理。一旦我们记住了作用域以及它们是如何链接在一起的,我们就可以更清楚地了解为什么其他“现代”方式,如Array.prototype.forEachfunction callbackel{}自然工作:传入的回调自然形成了包装作用域,在forEach的每次迭代中,el都正确绑定。因此,回调中定义的每个内部函数都将能够使用正确的elvalue@TinyGiant返回函数的示例仍然针对性能进行了优化。我不会像所有的JavaScript博客作者那样随波逐流。它们看起来很酷也很干净,但提倡内联编写函数,而不是使用预定义函数。在炎热的地方,这可能是一个不明显的陷阱。另一个问题是,它们不仅仅是语法糖,因为它们正在执行不必要的绑定,从而创建包装闭包。这段代码不做类似的事情。在这里,您所做的只是从接受的答案中提取代码,移动一些东西,稍微更改样式和命名,然后将其称为currying,这在分类上是不正确的。const提供相同的结果,并且应该在变量的值不会更改时使用。然而,for循环的初始值设定项中const的使用在Firefox中实现不正确,尚未修复。它不是在块内声明,而是在块外声明,这将导致对变量的重新声明,从而导致错误。在初始化器中使用let在Firefox中是正确实现的,因此不必担心。现在JavaScript中有了使用let和const关键字的块作用域。如果这个答案扩展到包括这一点,我认为它在全球范围内会更有用。@TinyGiant当然,我添加了一些关于let的信息,并链接了一个更完整的explanation@woojoo666您的答案是否也适用于在循环中调用两个交替URL,如:i=0;当小于100{setTimeoutfunction{窗口时。openhttps://www.bbc.com,_self},3000;setTimeoutfunction{window。openhttps://www.cnn.com,_self},3000;i++}?可能会将window.open替换为getelementbyid……@NuttyAboutn很抱歉回复这么晚。您的示例中的代码似乎已经不起作用了。您在超时函数中没有使用i,因此不需要closurewhoops,抱歉,这意味着您的示例中的代码似乎已经可以工作了关于}i@aswzen我认为它将I作为参数索引传递给函数。它实际上是在创建局部变量索引。立即调用函数表达式,也称为IIFE。i是立即调用的匿名函数表达式的参数,索引从i开始设置。小说明:在循环中闭包的第一个示例中,计数器[0]和计数器[1]不是独立的,不是因为性能原因。原因是var obj={counter:0};按照::var声明中的说明在执行任何代码之前求值,无论它们出现在何处,都会在执行任何代码之前进行处理
值得注意的是,变量也可以用let而不是const来声明。两者都允许块作用域变量。IIFE就是我想要的
funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};
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 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
[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3
var funcs = Query.range(0,3).each(function(i){
     return  function() {
        console.log("My value: " + i);
    };
});
funcs.iterate(function(f){ f(); });
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
}
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
}
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]();                        
}
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]();                        
}
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?
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
}
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
}
var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}
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);
  }
}
<script>
   var funcs = [];
   for (var i = 0; i < 3; i++) {
     funcs[i] = function () {
        debugger;
        console.log("My value: " + i);
     };
   }
   console.log(funcs);
</script>
<script>
    var funcs = [];
    for (let i = 0; i < 3; i++) {
        funcs[i] = function () {
           debugger;
           console.log("My value: " + i);
        };
    }
    console.log(funcs);
</script>