闭包的内存图解和原理

150 阅读3分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战

函数和闭包

函数是一等公民

函数是一等公民,意思是函数是非常重要的。

函数可以做为另一个函数的参数,也可以做为另一个函数的返回值来返回。

js中函数中仍然可以定义函数,实现函数嵌套。

闭包

闭包 :包含两部分,函数和自由变量。

严格来说就是函数内部访问了外部的自由变量,这就是一个闭包。

代码执行过程

   //闭包
         function foo() {
             var name = 'wang';
             var age = 13;
 ​
             function baz() {
                 console.log(name);
                 console.log(age);
             }
             return baz;
         }
 ​
         var fn = foo();
         fn();
1.在堆中创建go对象, 并且创建了执行上下文栈

1.png

2.在执行上下文栈中创建了全局执行上下文

全局执行上下文中先预解析代码,foo为函数,foo属性会保存foo函数的内存地址,fn为变量,初始值为undefined

2.png

3.开始执行foo函数前先解析foo函数

执行foo函数会创建foo函数执行上下文,当前的vo对应的函数AO,因此会创建AO对象,预解析foo函数中的变量,baz指向函数baz函数的内存地址

3.png

真正的执行foo函数

执行代码后,name:wang , age:13 ,return baz 最终全局执行上下文接受到foo的返回值并赋给了fn,因此fn保存了baz函数对象的地址

4.png

foo函数执行完毕

foo执行完毕以后,相应的函数执行上下文会被销毁,正常情况下foo的AO对象也应该销毁,但此时baz函数的父级作用域指向的是foo的AO对象,所以不会被销毁。

5.png

执行fn函数

执行fn函数实际上是执行baz函数。首先,同样是创建baz函数执行上下文,创建baz的AO对象,因为没有定义变量,所以baz的AO对象为空,然后直接执行代码,打印name和age。因为当前AO对象中没有name和age属性,所以会从他的父级AO对象中查找name和age,查找都父级AO(foo)对象的name和age后,打印出来。

6.png

执行完fn函数

执行完fn函数以后,创建的函数执行上下文和AO对象都会被销毁。但foo的AO对象,因为一直有baz函数的parent scope 指向他,所以一直不会被释放。所以可能会出现内存泄漏,当我们不再使用它的时候可以通过 fn=null 来释放闭包,防止内存泄漏.

7.png

AO中不使用的变量

闭包可以访问外层AO的变量,使得外层AO不会被销毁,但是外层AO中不使用的变量,按道理应该不被销毁,因为整个AO都不会被销毁,但v8引擎做了优化,不使用的变量会被销毁,防止了内存泄漏。

 function foo() {
     var name = 'wang';
     var age = 12;
 ​
     function baz() {
         debugger //设置断点
         console.log(name);
     }
     return baz;
 }
 ​
 var fn = foo();
 fn();

8.png

可以看出闭包Closure中只剩下name变量。