什么是内存泄漏,哪些常见操作会造成内存泄漏?
内存泄漏:指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。
1. 介绍
JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收系统(GC)会按照固定的时间间隔,周期性的执行。
怎么区分变量是否没有用,
垃圾收集器通常情况有两种方式跟踪变量
- 对于不再有用的变量打上标记,以备将来收回其内存 --> 标记清除(常用)
function test(){
var a=10;//被标记,进入环境
var b=20;//被标记,进入环境
}
test();//执行完毕之后a、b又被标记离开环境,被回收
- 跟踪记录每个值被引用的次数 --> 引用计数
function test(){
var a={};//a的引用次数为0
var b=a;//a的引用次数加1,为1
var c=a;//a的引用次数加1,为2
var b={};//a的引用次数减1,为1
}
2. 造成泄露的操作
- 意外的全局变量引起的内存泄露
一个未声明变量的引用会在全局对象中创建一个新的变量。在浏览器的环境下,全局对象就是 window,如下:
function fn(val) { b = "aaaaa"; } // 实际上等价于 function fn(val) { window.b = "aaaaa"; } // 类似的 function fn() { this.name = "zhangsan"; } //this 指向全局对象(window) fn();
- 闭包引起的内存泄露
闭包可以维持函数内局部变量,使其得不到释放。
由于是函数内定义函数,并且内部函引用了外部函数声明的变量–这就是闭包。function fn1(){ var n=1; function fn2(){//在加一个fn2当他的子集 alert(n); } return fn2(); //return出来后 他就给 window了所以一直存在内存中。因为一直在内存中,在IE里容易造成内存>泄漏 } fn1();
2.1 还一种闭包引起的内存泄露
function bindEvent(){ var obj=document.createElement("XXX"); obj.οnclick=function(){ //Even if it's a empty function } }
解决之道,将事件处理函数定义在外部,解除闭包,
//将事件处理函数定义在外部 function onclickHandler(){ //do something } function bindEvent(){ var obj=document.createElement("XXX"); obj.οnclick=onclickHandler; }
或者在定义事件处理函数的外部函数中,删除对dom的引用。
//在定义事件处理函数的外部函数中,删除对dom的引用 function bindEvent(){ var obj=document.createElement("XXX"); obj.οnclick=function(){ //Even if it's a empty function } obj=null; }
- dom对象导致的内存泄漏
//1、给DOM对象添加的属性是一个对象的引用。范例: var MyObject = {}; document.getElementById('myDiv').myProp = MyObject;
解决方法:
在window.onunload事件中写上: document.getElementById('myDiv').myProp = null;
//2、DOM对象与JS对象相互引用。范例: function Encapsulator(element) { this.elementReference = element; element.myProp = this; } new Encapsulator(document.getElementById('myDiv'));
解决方法:
在onunload事件中写上: document.getElementById('myDiv').myProp = null;
//3、给DOM对象用attachEvent绑定事件。范例: function doClick() {} element.attachEvent("onclick", doClick);
解决方法:
在onunload事件中写上: element.detachEvent('onclick', doClick);
- 被遗忘的定时器或者回调
var someResouce=getData(); setInterval(function(){ var node=document.getElementById('Node'); if(node){ node.innerHTML=JSON.stringify(someRe>souce) } },1000)
解决方法:
clearTimeout(***) 当不需要setInterval或者setTimeout时,定时器没有被clear,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。如果vue使用了定时器,需要在beforeDestroy 中做对应销毁处理。
- 循环引用
//反复重写同一个属性会造成内存大量占用(但关闭IE后内存会被释放)。范例: for(i = 0; i < 5000; i++) { hostElement.text = "asdfasdfasdf"; }
这种方式相当于定义了5000个属性!
解决方法:
其实没什么解决方法:就是编程的时候尽量避免出现这种情况
- 子元素存在引起的内存泄露
// 从外到内执行appendChild。这时即使调用removeChild也无法释放。范例: var parentDiv = document.createElement("div"); var childDiv = document.createElement("div"); document.body.appendChild(parentDiv); parentDiv.appendChild(childDiv);
解决方法:
//从内到外执行appendChild:var parentDiv = document.createElement("div"); var childDiv = document.createElement("div"); parentDiv.appendChild(childDiv); document.body.appendChild(parentDiv);
- 如果在mounted/created
钩子中使用了on,需要在beforeDestroy中做对应解绑(on,需要在beforeDestroy中做对应解绑(off)处理
beforeDestroy() { this.bus.$off('****'); }
- 死循环
while(1){ a++; }
解决方法:加条件
总结
- 减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;
- 注意程序逻辑,避免“死循环”之类的 ;
- 避免创建过多的对象 原则:不用了的东西要及时归还。