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

JavaScript

西门达蒙

2020-03-09

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

它输出:

My value: 3
My value: 3
My value: 3

Whereas I'd like it to output:

My value: 0
My value: 1
My value: 2


The same problem occurs when the delay in running the function is caused by using event listeners:

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>

… or asynchronous code, e.g. using 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));
}

What’s the solution to this basic problem?

第170篇《循环内的JavaScript闭合–简单的实际示例》来自Winter(https://github.com/aiyld/aiyld.github.io)的站点

14个回答
米亚十三Harry 2020.03.09

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

Tom小宇宙 2020.03.09

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

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

TomMandy 2020.03.09

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

飞云前端西门 2020.03.09

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

卡卡西Pro小卤蛋 2020.03.09

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

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

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失败和循环结束。在这一点上,iis 3和so何时funcs[someIndex]()使用和i评估,每次为3。

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

另一种选择是构造一个函数对象,该对象将能够覆盖变量。这样就可以做到

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};
十三西里GO 2020.03.09

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情况下,初始化变量在本例中被视为在花括号中声明。)

猴子阳光 2020.03.09

试试这个较短的

  • 没有数组

  • 没有额外的循环


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

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

http://jsfiddle.net/7P6EN/

L前端 2020.03.09

最简单的解决方案是

而不是使用:

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

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

阳光LEY 2020.03.09

这描述了在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

For each time makeCounter is invoked, {counter: 0} results in a new object being created. Also, a new copy of obj is created as well to reference the new object. Thus, counter1 and counter2 are independent of each other.

Closures in loops

Using a closure in a loop is tricky.

Consider:

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

Notice that counters[0] and counters[1] are not independent. In fact, they operate on the same obj!

This is because there is only one copy of obj shared across all iterations of the loop, perhaps for performance reasons. Even though {counter: 0} creates a new object in each iteration, the same copy of obj will just get updated with a reference to the newest object.

Solution is to use another helper function:

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封闭陷阱和用法

飞云Tom小宇宙 2020.03.09

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

SamJinJin路易 2020.03.09

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

Gil番长 2020.03.09

如今,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浏览器,边缘和Chrome。它在Node中也受支持,您可以利用Babel等构建工具在任何地方使用它。您可以在此处看到一个有效的示例:http : //jsfiddle.net/ben336/rbU4t/2/

此处的文档:

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

JinJin阿飞番长 2020.03.09

尝试:

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是现在执行此类操作的最佳方法。还有LO-破折号/下划线是_.partial当你不需要或不想要惹做bindthisArg

问题类别

JavaScript Ckeditor Python Webpack TypeScript Vue.js React.js ExpressJS KoaJS CSS Node.js HTML Django 单元测试 PHP Asp.net jQuery Bootstrap IOS Android