var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing)
console.log('hi');
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log('someMessage');
}
};
};
setInterval(replaceThing, 1000);
垃圾回收
引用计数
判断对象是否被其他对象引用,如果没有,则可回收(零引用)
缺陷:无法处理循环引用导致内存泄漏
例如:
上述示例中,即使没有再用到
obj1
,obj2
,但它们各自被引用了一次,因此不会被回收标记清除
从根对象(全局对象)开始遍历所有可以获得的对象,如果对象无法获得,则可以回收
上述循环引用的示例中,在函数调用后,从根对象开始都无法再获得
obj1
与obj2
,因此可以被回收限制:要清除一个对象,需要手动将它变成无法获得,比如
obj = null
等。个人认为这不是个问题。参考
MDN
引起内存泄漏的原因
全局变量
一般是无意中声明的,例如:
还有一种情况是
this
的使用问题:上述
this
在非严格模式下指向的是全局对象,可以通过use strict
避免这种情况的发生。计时器或回调
一般是使用
setInterval
忘了清除导致的,不仅回调内的变量不会被清除,回调函数引用的外层作用域的变量也无法被正常回收:还有一点是各类监听事件的回调,例如
addEventListeners
的回调,在不需要监听后,如果没有 remove ,在旧的 IE 下会导致泄漏,因为旧 IE 用的是引用计数算法,无法清除循环引用。尽管现代浏览器可以识别循环引用,开发者不必再手动 remove 监听的回调,但动手清理一下还是个比较好的实践,特别是在编写库的时候,可以防止在旧浏览器上出现这类泄漏
离 DOM 引用
有时候我们将 DOM 的引用存入 JS 数据结构中进行一些操作。当要删除这个 DOM 节点时,记得把 JS 代码中的这个 DOM 引用也释放掉:
闭包
上述代码中,
网上讨论是浏览器引擎作用域设计问题,不是泄漏问题,这边不做讨论。
识别内存泄漏的方法
浏览器方面,使用 Chrome devtools 的 Performance monitor 面板,一段时间内多次点击回收垃圾按钮(Collect garbage),如果
JS heap size
曲线呈现上升趋势而不是趋于平稳,则多半发生了内存泄漏问题。如何预防和解决内存泄漏问题
后话
对于闭包的问题,明天就 0202 年了,只要设计得当,对于闭包的利用价值是大于防止泄漏的,而且很大一部分泄漏问题是历史遗留问题,比如 IE 的垃圾回收 bug ,在今天大可不必刻意去避免闭包的使用。
参考