JS中4种常见的内存泄漏
一、什么是内存泄漏
本质上讲,内存泄漏是当一块内存不再被应用程序使用的时候,由于某种原因,这块内存没有返还给操作系统或空闲内存池的现象。
二、几种常见的内存泄漏
1、意外的全局变量
一个未声明变量的引用会在全局对象中创建一个新的变量。在浏览器的环境下,全局对象就是window,也就是说:
function foo(arg) { bar = "this is a hidden global variable"; }
实际上是:
function foo(arg) { window.bar = "this is an explicit global variable"; }
上面代码中,如果bar是一个应该指向foo函数作用域内变量的引用,但忘记使用var来声明这个变量,这时就相当于创建了一个全局变量。
另外一种偶然创建全局变量的方式如下:
function foo() { this.variable = "potential accidental global"; } foo();
上面代码中,foo函数再全局作用域中被调用,因此this指向window
全局变量的注意事项:
如果需要全局变量来存储很多数据,必须确保在使用过后将它设置为null或重新为他赋值。
常见的和全局变量相关的引发内存消耗增长的原因是缓存。缓存存储着可复用的数据。
为了让这种做法更高效,必须为缓存的容量规定一个上界。由于缓存不能被及时回收的缘故,缓存无限制地增长会导致很高的内存消耗。
2、闭包引起的内存泄漏
闭包可以使变量常驻内存,但如果使用不当就会在成内存泄漏
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);
上面代码中,每次调用 replaceThing
时,theThing
都会得到新的包含一个大数组和新的闭包(someMethod
)的对象。
同时,没有用到的那个变量持有一个引用了 originalThing
(replaceThing
调用之前的 theThing
)闭包。
关键的问题是每当在同一个父作用域下创建闭包作用域的时候,这个作用域是被共享的。在这种情况下,someMethod
的闭包作用域和 unused
的作用域是共享的。
unused
持有一个 originalThing
的引用。尽管 unused
从来没有被使用过,someMethod
可以在 theThing
之外被访问。
而且 someMethod
和 unused
共享了闭包作用域,即便 unused
从来都没有被使用过,它对 originalThing
的引用还是强制它保持活跃状态(阻止它被回收)。
当这段代码重复运行时,将可以观察到内存消耗稳定地上涨,并且不会因为 GC 的存在而下降。
本质上来讲,创建了一个闭包链表(根节点是 theThing
形式的变量),而且每个闭包作用域都持有一个对大数组的间接引用,这导致了一个巨大的内存泄露。
3、DOM之外的引用
var elements={ button: document.getElementById("button"), image: document.getElementById("image"), text: document.getElementById("text") }; function doStuff(){ image.src="http://some.url/image"; button.click(): console.log(text.innerHTML) } function removeButton(){ document.body.removeChild(document.getElementById('button')) }
2、被遗漏的定时器和回调函数
var someResouce=getData(); setInterval(function(){ var node=document.getElementById('Node'); if(node){ node.innerHTML=JSON.stringify(someResouce) } },1000)
上面代码中, 如果 id 为 Node 的元素从 DOM 中移除, 该定时器仍会存在, 同时, 因为回调函数中包含对 someResource 的引用, 定时器外面的 someResource 也不会被释放。
三、怎样避免内存泄漏
1)减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;
2)注意程序逻辑,避免“死循环”之类的 ;
3)避免创建过多的对象 原则:不用了的东西要及时归还。