JQuery垃圾收集 – 这会干净吗?
许多文章(例如msdn )都说当涉及DOM对象和JS对象时,在某些浏览器中无法清除循环引用。
(IE 6根本无法完成,IE7只能在页面请求之间执行):
Javascript Native( Leaks ):
function leak(){ var elem = document.createElement("DIV"); document.body.appendChild(elem); elem.onclick = function () { elem.innerHTML = elem.innerHTML + "."; // ... }; }
因为元素的onload属性通过闭包引用回自身,所以它创建了一个循环引用 :
elem [DOM] -> elem.onclick [JS] -> elem [DOM]
JQuery版本( 不泄漏 ):
function leak(){ var elem = $(''); $(document.body).append(elem); elem.click(function () { elem.html(elem.html() + "."); // ... }; }
在这种情况下,即使仍有循环引用,jQuery也会阻止所有相关浏览器中发生泄漏:
elem [JS] -> element [DOM] -> elem.onclick [JS] -> elem [JS]
我的问题:如果仍有循环引用,jQuery如何阻止泄漏?
jQuery代码中的最后一件事(在Sizzle的代码之前,它的选择器引擎)是这个(这是防止泄漏的代码):
// Prevent memory leaks in IE // Window isn't included so as not to unbind existing unload events // More info: // - http://isaacschlueter.com/2006/10/msie-memory-leaks/ if ( window.attachEvent && !window.addEventListener ) { window.attachEvent("onunload", function() { for ( var id in jQuery.cache ) { if ( jQuery.cache[ id ].handle ) { // Try/Catch is to handle iframes being unloaded, see #4280 try { jQuery.event.remove( jQuery.cache[ id ].handle.elem ); } catch(e) {} } } }); }
当你在jQuery中做任何事情时,它会存储它所做的事情(即函数)和什么(即DOM元素)。 onunload通过jQuery缓存从其自己的内部缓存的事件处理程序中移除函数(无论如何都存储事件而不是单个DOM节点)。
哦,行:
if ( window.attachEvent && !window.addEventListener ) {
确保它只在IE上运行。
JQuery只能确保在通过库进行所有操作时没有泄漏。 jQuery中有一些名为“empty”和“cleanData”的例程,你可以仔细查看它们究竟发生了什么,但基本上代码只是在释放它们之前从DOM元素中分离出它所知道的一切。 当您使用“.html()”或“.load()”覆盖元素内容时,会调用该例程。
我个人对这种情况下的“保证”等术语非常谨慎。
改写以进一步澄清
在提供的示例中,实际上有两个导致内存泄漏的原因。 第一个内存泄漏由于创建对闭包内的实时DOM节点的直接引用而显现。 诸如IE6的传统浏览器的垃圾收集器(JS和DOM)不能使这样的引用无效。 因此需要在函数末尾清空节点引用。
由于实时DOM元素作为属性/属性附加到jQuery对象,jQuery默认绕过这个,前面提到的垃圾收集器在确定空引用时没有任何问题。 如果jQuery对象具有空引用,则只需清除它,并将其属性/属性(在本例中为对实时DOM元素的引用)与其一起清除。
所以为了避免这种内存泄漏,就是让一个对象维护对live DOM节点的引用,然后引用你的闭包中的对象。 闭包只会维护对象的引用而不是live DOM元素,因为该引用属于对象。
// will still leak, but not due to closure references, thats solved. function noLeak(){ var obj= { elem: document.createElement('div') } obj.elem.onclick = function(){ obj.elem.innerHTML = obj.elem.innerHTML + "."; } }
这清除了最明显的循环引用,但仍然存在泄漏(onclick)。 该节点具有对函数的引用,该函数具有对对象的引用,该对象又具有对该节点的引用。 避免这种泄漏的唯一方法是从addEvent竞赛中学习(许多人努力解决这个漏洞;))。 巧合的是,所需的代码可以在其中找到,所以我的appologies没有提供代码;)
为事件系统创建包装器会添加更多代码,但这是必不可少的。 主要思想是添加一个公共eventHandler,它将事件委托给存储所需引用的事件缓存/系统。 在卸载事件中,缓存被清除,打破循环引用,允许垃圾收集器(JS和DOM)整理自己的角落。