3092

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

for infor of循环中也很明显:

const arr = [1,2,3];
const fns = [];

for(var i in arr){
  fns.push(() => console.log(`index: ${i}`));
}

for(var v of arr){
  fns.push(() => console.log(`value: ${v}`));
}

for(var f of fns){
  f();
}

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

4

44 回答 44

2327

好吧,问题在于i每个匿名函数内的变量都绑定到函数外的同一个变量。

ES6 解决方案:let

ECMAScript 6 (ES6) 引入了新的letconst关键字,它们的作用域不同于var基于 - 的变量。例如,在具有let-based 索引的循环中,循环中的每次迭代都会有一个具有循环范围的新变量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 一样记录 3 var)。Edge 14 终于做对了。


ES5.1 解决方案: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()函数会给你类似的能力。


经典解决方案:闭包

您要做的是将每个函数内的变量绑定到函数外的一个单独的、不变的值:

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”的值保持您的预期。

于 2009-04-15T06:18:17.527 回答
405

尝试:

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最近关于 using 的回答.bind是现在做这种事情的最佳方式。_.partial当您不需要或不想弄乱bind's时,还有 lo-dash/underscore's thisArg

于 2009-04-15T06:10:50.947 回答
376

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

于 2013-10-11T16:41:56.673 回答
285

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

于 2013-10-11T18:23:43.657 回答
176

聚会有点晚了,但我今天正在探索这个问题,并注意到许多答案并没有完全解决 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;
}

这正是这个错误如此棘手的原因。即使您正在重新声明一个变量,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 现在成为主流,我们现在可以使用 newlet关键字来创建块范围的变量:

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

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

于 2015-04-10T09:57:07.557 回答
164

随着 ES6 现在得到广泛支持,这个问题的最佳答案已经改变。ES6 为这种情况提供了letandconst关键字。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 一样记录 3 var)。Edge 14 终于做对了。

于 2013-05-21T03:04:48.667 回答
94

另一种说法是函数中的iin 在执行函数时绑定,而不是在创建函数时绑定。

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

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

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

于 2009-04-15T06:48:43.727 回答
77

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

于 2009-04-15T06:25:08.790 回答
64

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

于 2012-08-07T08:45:35.700 回答
62

这描述了在 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在循环的所有迭代中只有一个 shared 副本,可能是出于性能原因。即使{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);
  }
}

这是因为函数作用域中的局部变量以及函数参数变量在进入时被分配了新的副本。

于 2013-04-20T09:59:57.620 回答
52

最简单的解决方案是,

而不是使用:

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匿名函数内部定义的每个函数的值都是不同的。

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

于 2013-06-25T14:21:53.770 回答
34

这是一个使用的简单解决方案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
于 2014-05-03T03:42:57.677 回答
33

试试这个更短的

  • 没有数组

  • 没有额外的 for 循环


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

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

http://jsfiddle.net/7P6EN/

于 2013-09-19T14:20:24.317 回答
30

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的值。此时,is等时使用,评价时,每次为3-。3i < 3i3funcs[someIndex]()i

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

另一种选择是构造一个能够关闭变量的函数对象。可以这样实现

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};
于 2014-03-05T23:03:24.267 回答
24

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在我们的例子中,被认为是在花括号中声明的。)

于 2016-12-07T17:33:27.453 回答
15

在阅读了各种解决方案之后,我想补充一点,这些解决方案起作用的原因是依赖于范围链的概念。这是 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内部执行时的值。

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

于 2014-07-14T14:42:00.293 回答
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 问题中的代码被替换为let而不是var.

于 2016-11-07T11:25:54.803 回答
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而不是地图。

于 2014-12-09T22:24:17.963 回答
10

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

于 2018-01-13T13:17:57.497 回答
9

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

于 2009-04-15T06:18:29.510 回答
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. 我们可以i用初始化let或初始化一个新变量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]();                        
    }
    
于 2016-11-04T07:46:07.800 回答
8

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

var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}
于 2017-01-20T10:02:32.720 回答
8

我们将逐一检查您声明var时实际发生的情况。let

案例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对象中获取值并显示结果。

案例2:使用让

现在替换'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"。现在展开Blockobject ,你会看到 'i' 被定义在那里,奇怪的是,对于每个函数,if 的值i都是不同的 (0 , 1, 2)。

在此处输入图像描述

结论:

当你'let'在函数外而是在循环内声明变量时,这个变量将不是一个全局变量,它会变成一个Block级别变量,只对同一个函数可用。这就是原因,我们得到i不同的值对于我们调用函数时的每个函数。

有关更紧密的工作原理的更多详细信息,请观看精彩的视频教程https://youtu.be/71AtaJpJHw0

于 2018-01-16T14:29:57.130 回答
6

直到 ES5,这个问题只能使用闭包来解决。

但是现在在 ES6 中,我们有了块级作用域变量。将var更改为let in first for 循环将解决问题。

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 (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

于 2019-05-02T11:57:47.303 回答
6

while如果您遇到循环而不是循环的此类问题for,例如:

var i = 0;
while (i < 5) {
  setTimeout(function() {
    console.log(i);
  }, i * 1000);
  i++;
}

关闭当前值的技术有点不同。const在块内声明一个块范围的变量while,并将当前分配i给它。然后,在异步使用变量的地方,i用新的块范围变量替换:

var i = 0;
while (i < 5) {
  const thisIterationI = i;
  setTimeout(function() {
    console.log(thisIterationI);
  }, i * 1000);
  i++;
}

对于不支持块作用域变量的旧浏览器,您可以使用一个名为的 IIFE i

var i = 0;
while (i < 5) {
  (function(innerI) {
    setTimeout(function() {
      console.log(innerI);
    }, innerI * 1000);
  })(i);
  i++;
}

如果要调用的异步动作恰好setTimeout如上,还可以setTimeout第三个参数调用来表示调用传递函数的参数:

var i = 0;
while (i < 5) {
  setTimeout(
    (thisIterationI) => { // Callback
      console.log(thisIterationI);
    },
    i * 1000, // Delay
    i // Gets passed to the callback; becomes thisIterationI
  );
  i++;
}

于 2019-09-21T21:34:44.720 回答
4

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

于 2015-06-17T12:02:42.773 回答
4

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

这看起来比其他语言的范围更丑,但恕我直言,没有其他解决方案那么可怕。

于 2015-12-17T14:14:56.857 回答
4

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

于 2016-05-05T11:48:51.130 回答
3

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

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,所以当条件为假时它会立即停止,所以它是i = 3

你需要明白,当你的函数被创建时,它们的代码都不会被执行,它只是被保存以备后用。因此,当它们稍后被调用时,解释器会执行它们并询问:“当前的值是i多少?”

因此,您的目标是首先保存ito 函数的值,然后才将函数保存到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

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

于 2016-11-04T08:58:29.117 回答
3

许多解决方案似乎是正确的,但他们没有提到它被称为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);
  }
}

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

于 2017-02-28T15:09:04.190 回答
3
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
}
于 2018-07-13T08:02:09.890 回答
3

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

于 2019-04-15T10:37:39.027 回答
2

这是异步代码经常遇到的问题,变量i是可变的,并且在进行函数调用时,使用的代码i将被执行并且i会变异为其最后一个值,因此意味着在循环中创建的所有函数都将创建一个闭包,将等于 3(循环i的上限 + 1 。for

一种解决方法是创建一个函数,该函数将保存i每次迭代的值并强制复制i(因为它是一个原语,如果对您有帮助,请将其视为快照)。

于 2015-05-06T22:33:08.577 回答
2

计数器是一个原始的

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

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

超时完成后,它将为两者打印 2。这是因为回调函数根据词法范围访问值,它是定义函数的地方。

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

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
// ****************************
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
于 2017-07-17T10:10:55.673 回答
1

只需将 var 关键字更改为 let。

var 是函数范围的。

let 是块作用域。

当您开始编写代码时,for 循环将迭代并将 i 的值分配给 3,这将在整个代码中保持为 3。我建议您阅读有关节点范围的更多信息(let、var、const 等)

funcs = [];
for (let i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] =async function() {          // and store them in funcs
    await 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
}
于 2018-09-05T13:00:37.397 回答
0

让我们利用new Function. 因此i不再是闭包的变量,而只是文本的一部分:

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

for (var j = 0; j < 3; j++) {
    funcs[j]();
}
于 2016-11-11T09:43:17.493 回答
0

虽然这个问题很老并且已经回答,但我还有另一个相当有趣的解决方案:

var funcs = [];

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

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

变化是如此之小,几乎很难看出我做了什么。我将第二个迭代器从 aj 切换到 i。这会以某种方式及时刷新 i 的状态,从而为您提供所需的结果。我这样做是偶然的,但考虑到以前的答案是有道理的。

我写这篇文章是为了指出这个很小但非常重要的区别。希望这有助于消除像我这样的其他学习者的一些困惑。

注意:我没有分享这个,因为我认为这是正确的答案。这是一个脆弱的解决方案,在某些情况下可能会中断。实际上,我很惊讶它真的有效。

于 2018-02-25T03:15:21.233 回答
0

假设你不使用 es6;您可以使用 IFFY 功能:

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

但它会有所不同。

于 2018-03-14T06:19:14.140 回答
0

行。我通读了所有的答案。即使这里有一个很好的解释——我就是无法让它发挥作用。于是我就去网上找了。https://dzone.com/articles/why-does-javascript-loop-only-use-last-value的人有一个未在此处提供的答案。所以我想我会发布一个简短的例子。这对我来说更有意义。

总而言之,LET 命令很好,但现在才被使用。然而,LET 命令实际上只是一个 TRY-CATCH 组合。这可以一直追溯到 IE3(我相信)。使用 TRY-CATCH 组合 - 生活简单而美好。可能是 EMCScript 人决定使用它的原因。它也不需要 setTimeout() 函数。所以没有时间浪费。基本上,每个 FOR 循环都需要一个 TRY-CATCH 组合。这是一个例子:

    for( var i in myArray ){
       try{ throw i }
       catch(ii){
// Do whatever it is you want to do with ii
          }
       }

如果您有多个 FOR 循环,则只需为每个循环放置一个 TRY-CATCH 组合。另外,就个人而言,我总是使用我正在使用的任何 FOR 变量的双字母。所以“ii”代表“i”等等。我在例程中使用此技术将鼠标悬停命令发送到不同的例程。

于 2018-03-28T20:15:11.983 回答
0

这证明了关于“闭包”和“非闭包”如何工作的 JavaScript 是多么丑陋。

如果是:

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

funcs[i] 是一个全局函数,'console.log("My value: " + i);' 正在打印全局变量 i

如果是

var funcs = [];

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

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

由于 javascript 的这种扭曲的封闭设计,'console.log("My value: " + i);' 正在从外部函数“createfunc(i)”打印 i

这一切都是因为 javascript 不能像 C 编程语言那样在函数中设计像“静态”变量这样的体面的东西!

于 2018-08-01T09:32:05.987 回答
0
  asyncIterable = [1,2,3,4,5,6,7,8];

  (async function() {
       for await (let num of asyncIterable) {
         console.log(num);
       }
    })();
于 2019-04-07T16:45:19.850 回答
-1

为什么不直接在创建后的第一个(也是唯一一个)循环中调用每个函数,例如:

 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.
    };
    funcs[i]();// and now let's run each one to see
    }
于 2018-05-25T00:18:33.080 回答
-1

在 的支持下,最好的方法是针对这种情况ES6使用let和关键字。const所以var变量 gethoisted并且随着循环的结束,i所有...的值都会更新closures,我们可以使用let这样设置循环范围变量:

var funcs = [];
for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}
于 2019-04-17T08:10:22.780 回答
-2

这个问题已经有很多有效的答案了。不过,使用函数式方法的人并不多。这是使用该forEach方法的替代解决方案,它适用于回调和闭包:

let arr = [1,2,3];

let myFunc = (val, index) => { console.log('val: '+val+'\nindex: '+index); };

arr.forEach(myFunc);

于 2017-10-13T08:53:18.510 回答