【问题标题】:JQuery Garbage Collection - Will This Be Clean?JQuery 垃圾收集 - 这会干净吗?
【发布时间】:2023-03-19 12:47:01
【问题描述】:

许多文章(例如msdn)表示,当循环引用涉及DOM 对象和JS 对象时,在某些浏览器中无法清理循环引用。

(IE 6根本做不到,IE7只能在页面请求之间做):

Javascript 原生(泄漏):

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 = $('<div></div>');
    $(document.body).append(elem);
    elem.click(function () {
        elem.html(elem.html() + ".");
        // ...
    };
}

在这种情况下,即使仍然存在循环引用,jQuery 也会阻止在所有相关浏览器中发生泄漏:

elem [JS] -&gt; element [DOM] -&gt; elem.onclick [JS] -&gt; elem [JS]

我的问题:如果仍然存在循环引用,jQuery 如何阻止泄漏?

【问题讨论】:

    标签: javascript jquery memory-leaks


    【解决方案1】:

    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 上运行。

    【讨论】:

    • 因此它会移除元素并破坏 IE6/6 的循环引用。感谢您的洞察力
    【解决方案2】:

    只有当您通过库进行所有操作时,JQuery 才能确保没有泄漏。在 jQuery 中有名为“empty”和“cleanData”的例程,您可以仔细阅读它们以确切了解发生了什么,但基本上,代码只是在释放 DOM 元素之前将它知道的所有内容从 DOM 元素中分离出来。当您执行诸如使用“.html()”或“.load()”覆盖元素内容之类的操作时,将调用该例程。

    在这种情况下,我个人对“保证”之类的术语非常谨慎。

    【讨论】:

    • 我知道当你删除一个元素时它会清理所有东西(所以我的例子不是一个完美的例子),但例如在 IE6 中,如果导航到下一页时留下循环引用,内存会泄漏(当你不删除元素时)。
    • 啊,我明白你在说什么。好吧,jQuery 提供帮助的另一种方式是它避免将 anything 直接挂接到 DOM 节点上。
    • 它不会将任何东西与它挂钩,但从技术上讲,它会将自己与它挂钩,因此会将所有 jQuery 事件和数据挂钩到它(当您调用 .remove 时会正确清除这些事件和数据)。
    • 好吧,全局上下文中的普通 Javascript 对象与由添加到 DOM 元素的引用绑定的 Javascript 对象之间存在差异。这就是 IE 的问题所在——知道如何释放 DOM 节点的代码对释放 DOM 节点可能引用的 Javascript 对象一无所知。据我所知,全局 Javascript 上下文(“窗口”)没有这个问题;如果确实如此,我根本看不出 IE 怎么能继续运行任何时间!
    【解决方案3】:

    重写以进一步澄清

    在提供的示例中,实际上有 2 个导致内存泄漏的原因。第一次内存泄漏是由于在闭包内创建了对活动 DOM 节点的直接引用。 IE6 等旧版浏览器的垃圾收集器(JS 和 DOM)无法取消此类引用。因此需要在函数结束时清空节点引用。

    jQuery 在默认情况下规避了这一点,因为实时 DOM 元素作为属性/属性附加到 jQuery 对象,使用这些元素,上述垃圾收集器可以轻松确定空引用。如果 jQuery 对象有空引用,它会被简单地清理掉,并且它的属性/属性(在这种情况下是对活动 DOM 元素的引用)连同它一起。

    所以为了避免这种内存泄漏,就是让一个对象维护对活动 DOM 节点的引用,然后在闭包中引用该对象。闭包只会维护对对象的引用,而不是活动的 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 竞赛中学习(很多人都在努力解决此泄漏;))。巧合的是,可以在其中找到所需的代码,因此我很抱歉没有为此提供代码;)

    为事件系统创建一个包装器会增加一些代码,但这是必不可少的。主要思想是添加一个通用事件处理程序,将事件委托给存储所需引用的事件缓存/系统。在卸载事件中,缓存被清除,打破循环引用,允许垃圾收集器(JS 和 DOM)整理自己的角落。

    【讨论】:

    • 所有 jQuery 对象都引用了它们正在操作的 DOM 元素,因此技术上是 elem -> DOMELEMENT -> onload 函数 -> elem。所以发生了循环引用。在我真正的 JS 中,我需要使用闭包,所以我不能使 elem 无效。
    • “A”循环引用不是内存泄漏的原因;)原因是活动 DOM 元素的“THE”循环引用。见上面的附录。
    • jQuery 在自身内部仍然有对 DOM 元素的引用,即使事件/数据没有附加到 DOM 元素,仍然存在循环引用。 - 虽然你的最后一句话(在答案中)引起了我的兴趣
    • 不是这样,一个 jQuery 实例持有对一个(或多个)DOM 元素的引用,但除非您自己建立连接,否则 DOM 元素不持有对 jQuery 实例的引用。事件侦听器最终进入缓存系统,该系统在 window.onunload 事件上被清除,因此所有循环引用都被破坏。 jQuery 充当包装器,将操作和信息操作委托给和来回 DOM 元素,但非常注意不要在 DOM 元素上留下对自身的引用。
    猜你喜欢
    • 2019-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-17
    相关资源
    最近更新 更多