本文已参与「新人创作礼」活动,一起开启掘金创作之路。 前面有讲到不包含闭包的js函数调用前后内存的变化,具体见下述链接: juejin.cn/post/708756…
这次来看一下包含闭包的函数被调用前后内存的变化,先上一段代码:
var name = "echo";
function foo() {
var age = 18;
return function bar() {
console.log(age);
}
}
var fn = foo();
fn();
同样的,在执行全局代码之前,会对其进行解析,这一过程跟上述链接中提到的是一致的。
然后来看一下代码执行的过程
首先是第一行var name="echo"
这一步会去全局执行上下文中的VO也就是GO中查找name并将其修改为echo
2-7行为函数定义,并不会被执行
第8行 var fn=foo(); 这一步可以分为两个步骤
- 执行foo函数
- 将foo函数的返回值赋给fn
先看第一步执行foo函数
在执行foo函数前会创建一个foo函数的执行上下文,并在foo函数中创建foo函数的AO。在内存中的表示如下:
然后开始执行foo函数,var age=18;
执行这一步回去foo函数的AO中寻找到age并为其赋值为18
然后将bar函数作为返回值返回。并赋值给了fn。
因此GO中的fn由undefined变为了bar函数的引用,如下:
这个时候foo函数已经执行完毕是要调用垃圾回收器调用foo函数的执行上下文和堆空间内的foo函数的AO对象销毁掉。但其实最终的内存状态是下面这样的,堆空间的AO并没有被销毁
这是因为堆空间中的垃圾回收会对栈从根元素进行遍历,若到达不了该对象则将该对象清除。而对于foo函数的AO对象,我们可以来看一下,从栈出发是否可以到达。
首先是全局执行上下文中的VO指向堆中的GO,而GO中的fn执行bar函数对象,bar函数对象的parentScope又指向AO对象,所以是存在一个VO(全局执行上下文)->GO->bar函数对象->AO(foo);所以我们是可以从栈中到达AO中的。所以AO是不会被垃圾回收器清除的。 最后执行fn(),执行之前:
然后执行内部代码,consoLe.log(age);
会先去fn的VO中查找age,VO指向的是堆中fn的AO,查找不到age,所以会去其父级作用域foo的AO中查找,查找到了age,于是最终会打印18