循环内的闭包

3

我知道一个用这个代码记录0到9的方法:

编辑:来源

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
      console.log(i);
    }), 10)
}

jsfiddle

是将setTimeout自我调用并将i作为参数传递,像这样:

for(var i = 0; i < 10; i++) {
    setTimeout((function(i) {
      console.log(i);
    })(i), 10)
}

但是我已经测试过不传递i的情况下使setTimeout自我调用,它仍然有效:

for(var i = 0; i < 10; i++) {
    setTimeout((function() {
      console.log(i);
    })(), 10)
}

jsfiddle

我的问题:

  1. 为什么即使不传递i作为参数,它也可以工作?
  2. 有必要传递i吗?

3
你同样可以写成 console.log(i); setTimeout(undefined, 10);。那又怎样?它不起作用。 - Bergi
1
嗯,你有两个作用域:for循环和setTimeout。循环中有变量i,所以你不需要将i传递给setTimeout;它已经被引用了。 - TheGenie OfTruth
1
@JaeeunLee 我的意思是,你的两个片段都没有创建超时或异步记录值。 - Bergi
2
@JaeeunLee:感谢您添加代码片段的来源。不幸的是,它是错误的!请参考我们的规范问题以获得更好的参考(也可以在这里)。 - Bergi
1
@JaeeunLee:在循环结束后添加console.log("now")。你期望在超时日志之前看到它,不是吗? - Bergi
显示剩余6条评论
2个回答

8

问题

这不是一个闭包。

for(var i = 0; i < 10; i++) {
  setTimeout((function(i) {
    console.log(i);
  })(i), 10)
}

setTimeout函数期望的参数是一个函数,但你立即调用它了。因此,它立即记录了i的值,而没有等待。现在,setTimeout的第一个参数是你的匿名函数的返回值,也就是undefined

这里也是同样的问题:

for(var i = 0; i < 10; i++) {
   setTimeout((function() {
     console.log(i);
   })(), 10)
}

这将立即执行,不会等待10毫秒。唯一的区别是你没有传递i作为参数,但它会在父级作用域中寻找一个名为i的变量 - 而且确实存在。

如果将时间设置为1秒(1000),则会立即调用匿名函数。

解决方案

真正的闭包应该像这样:

没有参数:你会看到10次10,因为在内部函数执行时,循环已经完成,这意味着此时i等于10:

for(var i = 0; i < 10; i++) {
   (function() {
        setTimeout(function() {
         console.log(i);
      },10);
   })();
}

使用参数,您将获得预期结果:

for(var i = 0; i < 10; i++) {
   (function(i) {
        setTimeout(function() {
         console.log(i);
      },10);
   })(i);
}

谢谢!如果您能告诉我如何使它正常工作,那就太好了。 - Jaeeun Lee
当解决方案#1在创建时未捕获'i'的值时,它是否仍然是闭包?或者它确实捕获了值? - Jaeeun Lee
1
实际上,闭包的本质是为了保存环境,像第二个例子中保存某些变量和值一样。因此,闭包的定义不仅仅是一个内部函数。因此,如果你严格一点,我会说不,第一个例子不是闭包。 - ScientiaEtVeritas
第一个例子主要用于创建新的作用域。否则,如果setTimeout在该函数内部或直接在for循环中,输出结果是相同的。它们被称为立即调用函数表达式(IIFE)。 - ScientiaEtVeritas

3
在你的所有代码中,你使用setTimeout的第一个参数执行函数却没有返回任何东西。因此会返回undefined。
在下面的代码中,我也进行了自我调用,但是返回了一个函数,以便在特定间隔后由setTimeout执行。
这是闭包函数。请检查控制台日志。它以毫秒为单位打印时间戳。有一个10毫秒的间隙。

for (var i = 0; i < 10; i++) {
  setTimeout((function(i) {
    return function() {
      console.log(i, performance.now() + 'ms');
    }
  })(i), i * 10)
}

附加信息 在评论部分,我有一个查询

what's the point, when you could just put the setTimeout inside an anonymous function? So I guess this is the code you are suggesting.

for (var i = 0; i < 10; i++) {
    setTimeout(function() {
      console.log(i, performance.now() + 'ms');
    }, i * 10)
}

如果您运行此代码片段,每次记录都将获得10个日志。这就是闭包的美妙之处。当执行匿名函数时。
function() {
   console.log(i, performance.now() + 'ms');
}

它无法在当前作用域中找到i,并搜索它的父作用域。然后,它发现了i,但不幸的是,循环在此时已完成迭代,其值现在为10。因此,每次记录的值都是10。

这就是我们需要闭包的原因。


当然可以,但如果您只是将setTimeout放在匿名函数中,那还有什么意义呢? - user663031
@torazaburo,伙计,我已经更新了我的答案以适应你的解释。请让我知道这是否是你所指的东西,或者我有什么遗漏。 - Ayan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接