问题:循环内的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>

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

标签:javascript,loops,closures

回答1:

好吧,问题在于每个匿名函数中的变量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终于正确了。

回答2:

尝试:

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 的最新答案是最好的方法现在要做这种事情。当您不需要或不想与bindthisArg搞混时,还可以使用破折号/下划线的_.partial

回答3:

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

回答4:

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

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

回答5:

参加聚会有点晚,但是我今天正在探讨这个问题,并注意到许多答案并没有完全解决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来查看它:

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

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

回答6:

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终于正确了。

回答7:

另一种说法是,函数中的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]();
}

回答9:

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

回答10:

这描述了在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关闭的陷阱和用法

回答11:

最简单的解决方案是

代替使用:

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

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

它会发出3次警告" 2"。这是因为在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 (立即调用函数表达式)并传递new_i作为参数并将其捕获为i。由于匿名函数会立即执行,因此对于匿名函数内部定义的每个函数,i值是不同的。

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

回答12:

尝试这个较短的

  • 无数组

  • 没有多余的循环


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

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

http://jsfiddle.net/7P6EN/

回答13:

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

回答14:

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演示

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

回答15:

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在大括号中声明。)

回答16:

在阅读了各种解决方案之后,我想补充一点,那些解决方案起作用的原因是依赖于范围链的概念。这是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时,作用域链将成为functioninner->global。由于无法在function内部中找到变量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时,作用域链将变为functioninner->functionexternal。这次i可以在外部函数的作用域中找到,该作用域在for循环中执行了3次,每次都正确地绑定了i值。内部执行时,它将不使用window.i的值。

可以在此处
它包括在循环中创建闭包的常见错误,以及我们为什么需要闭包和性能考虑。

回答17:

借助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

回答18:

我很惊讶,没有人建议使用forEach函数来更好地避免(重新)使用局部变量。实际上,由于这个原因,我不再使用for(vari...)

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

///编辑为使用forEach而不是地图。

回答19:

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

回答20:

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

回答21:

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

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数组被初始化且< code> func 数组变为3,所以i=3。现在,当调用funcs[j]()时,它再次使用变量i,该变量已经增加到3。

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

  1. 我们可以使用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]();                        
    }
    
  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]();                        
    }
    

回答22:

使用关闭结构,这样可以减少您的额外操作for循环。您可以在单个for循环中完成此操作:

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

回答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'声明变量时,它将变为全局变量(您可以通过键入iwindow.i。它将返回3)。
  2. 除非您调用这些函数,否则声明的讨厌的函数将不会调用并检查该函数中的值。
  3. 调用该函数时,console.log("我的值:"+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'声明变量时,此变量将不是Global变量,它将成为Block级别的变量这仅适用于相同的函数。这就是原因,当我们调用函数时,每个函数的i值都不同。

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

回答24:

您可以对数据列表使用声明性模块,例如 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的作者,因此偏向于使用它,因此不要仅出于说明性的方式而将我的话作为对上述库的建议:)

回答25:

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

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

回答26:

还有另一种解决方案:无需创建另一个循环,只需将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 也可以解决该问题。

回答27:

许多解决方案似乎都是正确的,但他们没有提及它叫做 这是针对类似情况的功能编程设计模式。视浏览器而定,比绑定快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);
  }
}

请参见在不同浏览器中的性能提升

回答28:

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

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的值。 / p>

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

回答29:

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
}

回答30:

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

回到顶部