难度:容易
前言
我在面试的时候经常会与候选人探讨闭包内的变量存储,发现绝大多数的人仅仅止步于闭包的定义,却对闭包内的变量存储模糊不清,所以本文将试着通过对js引擎运行时heapdump分析来一探究竟。
闭包的示例
我们打开Chrome浏览器运行下面的代码:
function makeStevenFunc() {
var stevenx911_name = new Array(1000000).join('x'); //这里通过数组构造一个1MB大小字符串
function displayStevenName() { // 这里定义一个具名函数,方便我们查找
console.log(stevenx911_name);
}
return displayStevenName;
}
var myFunc = makeStevenFunc();
myFunc();
打开devtools
,切换至memory
界面,点击take heap snapshot
获取当前页面运行的内存快照
这里说明下,heapdump是对堆内存进行文件快照,从上述的结果中可以清晰地看到闭包中定义的基础类型string变量stevenx911_name
存放在堆中,所以我们可以确认闭包中的变量*会*存在堆(heap)中
。
但是makeStevenFunc
定义的所有变量都会放进闭包吗?我们改下代码继续看:
function makeStevenFunc() {
var stevenx911_name = new Array(1000000).join('x');
var stevenx911_desc = new Array(1000000).join('y'); // 增加这句,同样构造一个1MB大小字符串
function displayStevenName() {
console.log(stevenx911_name);
}
return displayStevenName;
}
var myFunc = makeStevenFunc();
myFunc();
我们再来看看heapdump结果:
答案似乎很明显,heapdump的size没有变化,示例代码形成的闭包并没有包含父函数中定义的所有变量
,仅仅将子函数引用到的变量放置了进来。
到这里,关于闭包中的变量存储位置就基本清楚了!细心的同学一定会发现,heapdump的操作是在上述代码执行结束时做的,但闭包中的变量空间并没有释放,垃圾回收器似乎无法回收,所以下面谈一谈闭包带来的问题:内存泄漏。
闭包引起的内存泄漏
在js中,闭包是内存泄漏发生的重灾区,所以在使用闭包时请务必小心,多做测试,我们来看一段引起内存泄漏的示例代码:
var funs = [];
function fun0(){
funs.push(getVar()); //外部变量持有闭包的引用
}
setInterval(fun0, 1000);
function getVar(){
var arr = new Array(1000000);
return function(){
console.log(arr);
}
}
在浏览器中执行这段代码后,我们观察Proformance Monitor
可以看到JS Heap Size
持续增长,内存占用不断上升,因为外部变量(全局)持有闭包的引用,如果发生在页面上,当内存用量耗尽时,页面就会卡死。解决的方法可以将内存分配操作(new)置于闭包之外,或者用完及时销毁(赋值为null)。
当然现实情况可能不是定时器,而多数是用户的操作,比如onclick
和onresize
等频繁被触发的事件回调。
总结
关于闭包中的变量存储,网络上已经很多文章来介绍,示意图很多,理论也很详实,如果想要了解更多,可以打开本文参考部分的引用链接进行深入阅读,另外,我认为在学习js语言时:ECMA规范说明是第一权威,其次是规范的实现——js引擎,再次就是各路视频博客了,所以本文旨在从运行时的角度让大家在引擎层面对闭包中的变量存储有一些浅层的认识。(完)
参考
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
https://developers.google.com/web/tools/chrome-devtools/memory-problems?hl=zh-cn
评论