一.闭包的理解
维基百科中解释:闭包又称词法闭包或者函数闭包,是一个结构体,存储了函数和一个关联的环境,和函数最大的区别就是,当捕捉闭包的时候,它的自由变量会在补充的时候被确定,即使脱离了捕捉的上下文,也能照成运行
MDN解释:一个函数对其周围的引用绑定在一起,这样的组合就是闭包
我的理解就是:
一个普通函数,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包
广义的角度: Js中函数都是闭包
狭义的角度:Js中的一个函数,如果访问了外层作用域的变量,那么它就是一个闭包
二. 闭包的访问过程(V8引擎)
以下代码举个例子:
function foo(){
var name = 'bazy'
function bar(){
console.log("引用外部作用域变量",name);
}
return bar
}
var fn = foo()
fn()
JavaScript在parse编译阶段,会在堆内存全局创建一个GO对象,包含全局的一些初始变量,并且赋值undefind
var GobalObject = {
+String,
+seTimeout,
+Data,
window: GobalObject //window.window.window
//自己定义的变量
fn:undefind
foo:undefind
}
当JavaScript 执行的时候会建立一个执行栈ECS,和一个堆内存, 当全局执行代码会创建一个全局执行上下文GEC,其中存入了两部分,第一部分是VO对象,指向的就是GO对象,而第二部分就是执行的代码体
![](https://i-blog.csdnimg.cn/blog_migrate/7fc1bf67338dafad90c4c522d7c9e3eb.png)
之后就开始执行代码,GO对象会被重新赋值,若遇到函数,会在堆内存中开辟一个内存,用来存放当前函数父级作用域和函数体,GO中fn 会指向当前对象的地址
var GobalObject = {
+String,
+seTimeout,
+Data,
window: GobalObject //window.window.window
//自己定义的变量
fn:0x100 //看这一部分
foo:0x100
}
此时堆内存会变为:
![](https://i-blog.csdnimg.cn/blog_migrate/7d5d7151e325964844e0271518162a19.png)
当foo函数执行前会创建一个函数执行上下文栈FEC包含两部分,并且在编译阶段的时候会创建一个AO对象,默认包含函数的形参,argments等,也有自己定义的变量,默认复制undefind,第一部分是VO,此时指向的是AO对象,VO+父级VO,this绑定等,另一部分是代码执行体,
![](https://i-blog.csdnimg.cn/blog_migrate/83b3be464c68763e905cb3b5c853a1dc.png)
当foo函数执行的时候,AO会重新赋值,由于foo函数内返回一个bar,因此GO也会发生相应变化
此时 foo函数AO会变成
var AO = {
name: 'bazy',
bar: 0x200
}
由于bar函数是被return 出去的,所以 全局GO会变化为
var GobalObject = {
+String,
+seTimeout,
+Data,
window: GobalObject //window.window.window
//自己定义的变量
fn:0x200 //看这一步
foo:0x100
}
此时bar函数也执行啦,那么bar 也会创建自己的AO对象,也会创建自己的调用栈、
![](https://i-blog.csdnimg.cn/blog_migrate/c78075a859321422941747c2cf636ca0.png)
此时bar函数会通过在分配函数的对象的时候确定了作用域,进入会一级一级向上查找,进而打印出结果
三.foo函数执行完,AO对象就会销毁,为什么bar函数依然可以访问外层foo函数的变量name(闭包就体现在这点)
首先我们需要了解一下,JavaScript垃圾回收机制,在JavaScript 主要采用了CG算法进行内存回收,它的的基本原理就是监听一个根对象,例如GO对象,定期检查,如果该对象中,某个属性没有指向堆内存中的某个对象地址,就会被回收
看上图可以由于foo函数内部返回一个bar函数,因此fn指向的最终地址为bar函数对象,函数foo在执行完以后就会销毁,但是它的AO对象,依然是被bar函数指着,而bar 函数,此时被fn指针指向,因此CG算法在检测的时候会检测到该对象仍然是被指针指向,所以就不会被销毁
四. 闭包的内存泄露
在上面的案例中,如果我们后续不在使用fn函数,那么该对象就应该被销毁掉,并且引用的父作用域也该被销毁掉
但是在全局作用域下fn变量对0x200 有引用,而0X200对 0x100有引用,所以最终这些内存是无法被释放掉的
那么该如何解决这个问题呢