关于JavaScript的内存泄漏的思考
1. 概念
1) 内存泄漏就是指程序中不再用到的对象依然占用的内存无法释放;
2) 程序中的内存过程:系统分配------程序使用 ------ 程序、系统释放
说到内存泄漏不得不提到垃圾回收机制
2. 垃圾回收机制
目前垃圾回收机制有两种:
1) 引用标记法
优势:简单
劣势:对于循环引用的对象无法清除
2) 标记清除法--解决了循环引用对象
3. 常见的内存泄漏
1)全局变量
对于定义的全局变量,由于挂在window上,除非刷新浏览器,这个变量就永远不会被回收
2)未销毁的定时器和回调函数
var serverData = loadData(); setInterval(function() { var renderer = document.getElementById('renderer'); if(renderer) { renderer.innerHTML = JSON.stringify(serverData); } }, 5000); // 每 5 秒调用一次
如果后续renderer元素被移除,此时定时器就没有任何作用,但是如果没有清楚定时器,定时器的内存就没有被回收,这样serverData也无法被回收
3)闭包
在 JS 开发中, 我们会经常用到闭包, 一个内部函数, 有权访问包含其的外部函数中的变量. 下面这种情况下, 闭包也会造成内存泄露.
var theThing = null; var replaceThing = function () { var originalThing = theThing; var unused = function () { if (originalThing) // 对于 'originalThing'的引用 console.log("hi"); }; theThing = { longStr: new Array(1000000).join('*'), someMethod: function () { console.log("message"); } }; }; setInterval(replaceThing, 1000);
这段代码,每次调用replaceThing时,theThing获得了包含一个巨大的数组和一个对于新闭包someMethod的对象,同时unused是一个引用了originalThing的闭包。
一旦为同一父作用域中的闭包创建了闭包范围,就会共享作用域。
这个范例的关键在于,闭包之间是共享作用域,尽管unused可能一直没有被调用,但是someMethod可能会被调用,这样就会导致无法对其进行回收
修改上述问题:
var theThing = null; var replaceThing = function(){ var originalThing = theThing; //定义一个引用originalThing但不会 //实际被调用的闭包。但是因为这个闭包存在,所以 // originalThing将在词汇环境中用于在replaceThing中定义的所有//闭包,而不是在其中进行优化 //。如果删除此功能,则无泄漏。 var unused = function(){ if(originalThing) console.log(“hi”); }; theThing = { longStr:new Array(1000000).join('*'), //虽然这个 //函数理论上可以访问originalThing ,但它显然不会使用它。但是因为 // originalThing是词法环境的一部分,someMethod //将保存对originalThing的引用,所以即使我们 //正在用无效的方式替换theThing //引用旧的值ofThing,旧值 //永远不会被清理干净! someMethod:function(){} }; //如果在这里添加`originalThing = null`,则没有泄漏。 }; setInterval(replaceThing,1000);
4)DOM引用
很多时候,我们对DOM的操作会把DOM的引用保存在一个数组或者Map中
var elements = { image: document.getElementById('image') }; function doStuff() { elements.image.src = 'http://example.com/image_name.png'; } function removeImage() { document.body.removeChild(document.getElementById('image')); // 这个时候我们对于 #image 仍然有一个引用, Image 元素, 仍然无法被内存回收. }
上述案例中,即使我们对于image元素进行了移除,但是仍然有对image元素的引用,依然无法进行内存回收
另外需要注意的一个点是, 对于一个 Dom 树的叶子节点的引用. 举个例子: 如果我们引用了一个表格中的 td 元素, 一旦在 Dom 中删除了整个表格, 我们直观的觉得内存回收应该回收除了被引用的 td 外的其他元素. 但是事实上, 这个 td 元素是整个表格的一个子元素, 并保留对于其父元素的引用. 这就会导致对于整个表格, 都无法进行内存回收. 所以我们要小心处理对于 Dom 元素的引用.
参考原文:
https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156