先看一道闭包作用域练习题(浏览器垃圾回收机制)
下面代码的输出结果
let x = 5;
function fn(x) {
return function(y) {
console.log(y + (++x));
}
}
let f = fn(6);
f(7);
fn(8)(9);
f(10);
console.log(x);
解析图中的执行上下文都会进栈执行,但是为了简便此处省略了执行环境栈,但是执行上下文依然会进栈执行。
解析图如下:
- 先开辟一个全局执行上下文EC(G),再EC(G)中有一个存储全局变量的对象VO(G).
- 代码开始执行,先创建一个值5放进栈内,再创建一个变量名x,最后将两者关联起来。
- 先创建一个函数,开辟一个堆内存空间,这个函数堆又一个16进制的地址【0X001】,这个函数的作用域[[scope]]:EC(G),作用域就是函数创建时的执行上下文;此函数有形参x;函数体内的代码以字符串的形式保存在函数堆内。将这个堆内存地址0X001放在栈内存,创建一个函数名fn,最后将两者关联起来。
- 【let f = fn(6);】函数fn执行,开辟一个全新的私有的栈内存EC(FN1),这个栈内存进栈执行,返回一个小函数的堆内存0x101,将这个堆内存地址放进栈内存,创建一个变量名f,最后两者关联。
- 全新的私有的栈内存EC(FN1)不被释放,形成闭包,将自己私有的变量保护和保存起来。
- 【f(7);】函数f执行,形成一个新的执行上下文EC(F1),进栈执行后因为EC(F1)内没有被外界占有的内容,所以执行完后出栈释放。【f(10);】执行同上,也是开辟一个全新的执行上下文。
- 【fn(8)(9);】先执行大函数【fn(8)】,开辟一个全新的栈内存,这个外层函数暂时不会被释放,因为它的返回值也是一个函数,此内层函数接着执行【fn(8)】(9),内存函数执行也会开辟一个新的栈内存,进栈执行后出栈释放,外层函数接着也出栈释放。
如果想把栈内存EC(FN1)释放掉,需要手动设置f=null,解除变量名f对栈内存EC(FN1)内的堆内存0x101的占用,此栈内存EC(FN1)内的内容没有被外界占有了,就会出栈释放掉。
fn = null.释放0x001
f= null. 释放0x101 -> EC(FN1)也会被释放
从此道题引申出垃圾回收机制:
GC:浏览器的垃圾回收机制「内存管理」
谷歌- ->查找引用
浏览器的渲染引|擎会在空闲的时候(定期一个时间),依次遍历所有的内存:栈/堆
堆:当前堆内存如果被占用(指针关联地址),则不能释放,如果没有任何的事物占用这个堆,则浏览器会自动把这个堆内存释放掉;
栈:当前上下文中是否有内容(一般是堆内存)被上下文以外的事物所占用,如果被占用则无法释放「闭包」,如果没有被占用则释放掉; -> EC(G)是在加载页面的时候创建,只有关闭页面的时候才会被释放;
IE ->引用计数
每一个内存中都有-个数字N,记录被占用的次数
如果当前内存被占用一次,则内存中的N会累加一次,反之取消占用,N会累减;知道N为0,则释放内存;
下面的方案经常导致内存泄漏思考题: 回去后总结内存泄漏的出现情况「高程三最后章节」
把占用的事物手动赋值为null (其余的值也可以,但是null更好「null不占空间的」),可以实现内存的手动优化
…