首页 文章

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

提问于
浏览
2545
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
}

它输出这个:

我的 Value :3我的 Value :3我的 Value :3

而我希望它输出:

我的 Value :0我的 Value :1我的 Value :2


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

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

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

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

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

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

30 回答

  • 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
    }
    

    打印:

    我的 Value :0
    我的 Value :1
    我的 Value :2

  • 7

    好吧,问题是每个匿名函数中的变量 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++) {
        funcs[j]();                        // and now let's run each one to see
    }
    

    由于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 循环一起使用的回调函数的每次调用都将是它自己的闭包 . 传递给该处理程序的参数是特定于该迭代的特定步骤的数组元素 . 如果它与在迭代的其他步骤中 Build 的任何其他回调冲突 .

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


    ES6解决方案:让

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

    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最终做对了 .

  • 360

    尝试:

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

    Edit (2014):

    我个人认为@ Aust的more recent answer about using .bind是现在做这种事情的最佳方式 . 有's also lo-dash/underscore' s _.partial 当你不需要或想要弄乱 bindthisArg 时 .

  • 13

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

    使用Immediately-Invoked Function Expression,最简单,最易读的方法来封装索引变量:

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

  • 23

    派对迟到了,但我今天正在探讨这个问题,并注意到许多答案并没有完全解决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 的最后一个值 . 但是't each iteration have it'不应该 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中,内部函数可以访问外部变量,因为内部作用域“包围”外部作用域 .

    Closures

    这也意味着内部函数"hold onto"外部变量并保持它们活着,即使外部函数返回 . 为了利用这个,我们创建并调用一个包装器函数纯粹是为了创建一个新的作用域,在新作用域中声明 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]();
    }
    

    在包装函数中创建内部函数为内部函数提供了一个只有它才能访问的私有环境"closure" . 因此,每次调用包装器函数时,我们都会使用它自己独立的环境创建一个新的内部函数,确保 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);
        };
    }
    

    Update

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

    看看它现在多么容易!有关详细信息,请参阅this answer,我的信息基于 .

  • 147

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

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

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

    然后 val 将指向特定于循环的特定转弯的对象,并且将返回正确的值而不使用附加的闭包符号 . 这显然简化了这个问题 .

    const 类似于 let ,其附加限制是变量名称在初始赋值后无法回弹到新引用 .

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

    文件在这里:

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

  • 2

    另一种说法是函数中的 i 在执行函数时被绑定,而不是创建函数的时间 .

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

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

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

  • 8

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

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

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

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

  • 133

    这描述了在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 closure pitfalls and usage

  • 251

    最简单的解决方案是,

    而不是使用:

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

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

  • 49

    试试这个较短的一个

    • 没有数组

    • 没有额外的循环

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

    http://jsfiddle.net/7P6EN/

  • 331

    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 Functions close over the scope they are declared in, and retain access to that scope even as variable values inside of that scope change."

    使用 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 被认为是在大括号中声明 . )

  • 3

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

    • 每个函数定义形成一个范围,该范围由 var 及其 arguments 声明的所有局部变量组成 .

    • 如果我们在另一个(外部)函数中定义了内部函数,则会形成一个链,并将在执行期间使用

    • 执行函数时,运行时通过搜索 scope chain 来评估变量 . 如果可以在链的某个点找到变量,它将停止搜索并使用它,否则它将一直持续到达到属于 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 声明也未作为参数传递),它继续搜索,直到 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 的值 .

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

  • 7

    通过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 .

  • 3

    我很惊讶没有人建议使用 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 .

  • 65

    这个问题真的展示了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>
    
  • 82

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

    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 .

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

    • 我们可以用 let 初始化 i 或用 let 初始化一个新变量 index 并使其等于 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]();                        
    }
    
    • 其他选项可以引入 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]();                        
    }
    
  • 12

    我们将检查,当你声明var并让它一个接一个时,实际发生了什么 .

    案例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 console window 并刷新页面 . 在数组中扩展每3个函数 . 您将看到一个名为 [[Scopes]] .Expand的属性 . 您将看到一个名为 "Global" 的数组对象,展开该对象 . 您将在对象中声明属性 'i' ,其值为3 .

    enter image description here

    enter image description here

    Conclusion:

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

    • 您声明的不良函数不会调用并检查该值除非你调用函数,否则在函数内部 .

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

    CASE2:使用let

    现在用 'let' 替换 'var'

    <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) .

    enter image description here

    Conclusion:

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

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

  • 8

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

  • 6

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

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

    我更喜欢使用 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
    }
    

    这看起来比其他语言的范围更丑,但恕我直言比其他解决方案更怪异 .

  • 2
    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
    }
    
  • 2

    您可以将声明模块用于数据列表,例如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的作者,因此偏向于使用它,所以不要仅仅因为声明性方法而把我的话作为所述库的推荐:)

  • 1

    许多解决方案似乎都是正确的,但它们不会被称为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);
      }
    }
    

    the performance gain in different browsers .

  • 22

    您的代码不起作用,因为它的作用是:

    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 . 这可以通过以下方式完成:

    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 的值 .

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

  • 1963

    还有另一种解决方案:只需将 this 绑定到返回函数,而不是创建另一个循环 .

    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
    }
    

    通过绑定 this ,也解决了这个问题 .

  • 53

    COUNTER BEING A PRIMITIVE

    让我们定义回调函数如下:

    // ****************************
    // COUNTER BEING A PRIMITIVE
    // ****************************
    function test1() {
        for (var i=0; i<2; i++) {
            setTimeout(function() {
                console.log(i);
            });
        }
    }
    test1();
    // 2
    // 2
    

    超时完成后,将为两者打印2 . 这是因为回调函数基于lexical scope访问该值,其中定义了函数 .

    要在定义回调时传递和保留值,我们可以创建一个closure,以在调用回调之前保留该值 . 这可以按如下方式完成:

    function test2() {
        function sendRequest(i) {
            setTimeout(function() {
                console.log(i);
            });
        }
    
        for (var i = 0; i < 2; i++) {
            sendRequest(i);
        }
    }
    test2();
    // 1
    // 2
    

    现在有什么特别之处是“原语是通过值传递并复制的 . 因此,当定义闭包时,它们会保留前一循环的值 . ”

    COUNTER BEING AN OBJECT

    由于闭包可以通过引用访问父函数变量,因此这种方法与基元的方法不同 .

    // ****************************
    // COUNTER BEING AN OBJECT
    // ****************************
    function test3() {
        var index = { i: 0 };
        for (index.i=0; index.i<2; index.i++) {
            setTimeout(function() {
                console.log('test3: ' + index.i);
            });
        }
    }
    test3();
    // 2
    // 2
    

    因此,即使为作为对象传递的变量创建了闭包,也不会保留循环索引的值 . 这是为了表明不会复制对象的值,而是通过引用访问它们 .

    function test4() {
        var index = { i: 0 };
        function sendRequest(index, i) {
            setTimeout(function() {
                console.log('index: ' + index);
                console.log('i: ' + i);
                console.log(index[i]);
            });
        }
    
        for (index.i=0; index.i<2; index.i++) {
            sendRequest(index, index.i);
        }
    }
    test4();
    // index: { i: 2}
    // 0
    // undefined
    
    // index: { i: 2}
    // 1
    // undefined
    

相关问题