你真的了解JS的 ‘闭包’ 吗?
在真正了解闭包之前先问一个小问题d1的执行结果是什么?
答案是 3 这点毋庸置疑,有没有想过为什么呢?为什么当demo(1)执行完成之后 a 的值会被保留,为什么demo(1)执行以后 a 的值没有被js的垃圾回收机制所回收呢?
下面我将通过谷歌浏览器打断点的形式,分析上述代码在执行过程中变量的变化
1、执行脚本
此时在内存中声明了一个 d1 的变量, d1的值为 undefined
2、demo的执行
demo 执行时观察右侧作用域会发现 demo 会开辟出来一个独立的作用域用来存储当前函数执行时需要的变量,a 为这个临时作用域里的变量,值为 1
3、 demo执行结束,临时作用域消失??
demo 执行结束此时观察作用域,发现此时只剩下一个变量 d1 存储着一个函数,刚刚的临时作用域好像是消失了?难道刚刚作用域里的值被回收了吗?在这里我们不得而知,那我们就继续往下看
4、d1的执行
呦呦呦!好像发生了什么不得了的事情,执行完的 demo 里的临时变量 a 竟然没有被回收重新出现了,那么是为什么呢?为什么 demo 执行完了但是局部作用域里的变量没有被回收呢?
总结
原来在 demo 执行的时候发现 a 被里面的函数使用着, a 被标记着所以说 a 不会被回收。那么demo 执行完成之后 a 被存在哪里呢?原来JS在执行一个函数时,都会创建一个作用域对象(scope object),用来保存在这个函数中创建的局部变量。它将一切被传入函数的变量进行初始化,这与那些保存着所有全局变量和函数的全局对象(global object) window 相类似,但又有一些很重要的区别:
第一,每次函数被执行的时候,就会创建一个新的,特定的作用域对象;
第二,与全局对象(如浏览器的 `window` 对象)不同的是,你不能从 JavaScript 代码中直接访问作用域对象,也没有可以遍历当前作用域对象中的属性的方法。(也就是说你不能直接使用这个作用域对象)
所以,当调用 demo 时,解释器创建了一个作用域对象,它带有一个属性:a,这个属性被当作参数传入 demo 函数。然后 demo 返回一个新创建的函数(暂记为 d1)。通常,JavaScript 的垃圾回收器会在这时回收 demo 创建的作用域对象(暂记为 b),但是 demo 的返回值,在新函数 d1,拥有一个指向作用域对象 b 的引用。最终,作用域对象 b 不会被垃圾回收器回收,直到没有任何引用指向新函数 d1。
一个闭包,就是 一个函数 与其 被创建时所创建的 作用域对象 的组合。闭包允许你保存状态——所以,它们可以用来代替对象。