closure是什么时候生成的,具体又是怎样工作的
发布于 3 年前 作者 Even0311 2058 次浏览 来自 问答

学完 js ES6后有一些问题

我们都知道用var和let声明变量之间的区别,var声明会被提升到函数头部而let不会有任何提升,let是block级的scope而var是function或者global级的scope。 但是遇到了一个经典题目的时候产生了一些疑问

		

		var arr = [1, 2, 3];



		for (var i = 0; i < arr.length; i++) {

			 setTimeout(function () {

   				 console.log(arr[i]);

			 }, 1000);

		}

	这段代码的输出是 三个undefined,如果希望他正常输出1,2,  3 需要使用let声明循环变量,官方给出的解释是var的scope是function, let声明变量的scope是block。但是当我深入探究他的原理的时候有一点没有想明白。 在循环体中每一次运行setTimeout函数会向计时器模块发送异步任务,该异步任务的回调函数是console.log(arr[i]), for循环结束之后主循环就结束了,所有变量被弹栈,但是由于回调函数需要使用到数组arr和变量i,所有arr和i被称为捕获变量,生成一个closure 存在堆内存中。

我的问题是 
  1. 在循环体中,是否每一次调用setTimeout都会为匿名函数生成一个closure,如果是的话,是否是立即生成,如果是立即生成,那此时closure里面的i值应该是正确的, 从结果来看并不是这样
  2. 在i=0时候调用setTimeout, 立即为匿名函数生成了一个closure,在之后的循环中匿名函数也指向这个closure,但是 本地栈内存也可以修改这个closure, 导致最后调用console的时候取i值是变化后的i
  3. 在调用setTimeout的时候不会生成closure, 之后当console log被调用的时候才会生成closure, 如果这样的话那么毫无疑问这个closure里的i值就一定是3. 4.本地函数在被调用的时候就同步生成一个closure,如果子函数被补货了就在堆内存中保留这个closure,如果没有被补货就删除这个closure, 所以每一次循环体内的cons ole都指向这个被修改过的closure。 我不清楚具体的流程是怎么样的,希望有大神可以回答一下。
3 回复

故事略微有点长,如果你真的有兴趣的话,可以看 闭包是什么 闭包的作用

是否生成 closure 是在编译期间, 静态分析就能得到的, 然后会生成创建closure的代码(字节码? 虚拟机指令?)

如果改成 console.log(i); debugger 断点后可以看到 Local 以及 Closure, Closure 里面没有 arr 了

类似这个 image.png

另外, 在C语言里面(可以理解为适合人类阅读的字节码/指令), 是可以精确控制按值, 还是按引用 读写变量的, 闭包很显然是全按引用读写

@netwjx 谢谢您的解答,全按引用读写瞬间让我全都懂了哈哈。 感谢

回到顶部