【问题标题】:jQuery memory leak patterns and causesjQuery 内存泄漏模式和原因
【发布时间】:2011-06-30 01:58:09
【问题描述】:

jQuery 中有哪些标准问题或编码模式会导致内存泄漏?


我在 StackOverflow 上看到了许多与 ajax() 调用或 jsonp 或 DOM 删除相关的问题。大多数 jQuery 内存泄漏问题都集中在特定问题或浏览器上,如果能列出 jQuery 中的标准内存泄漏模式,那就太好了。

以下是关于 SO 的一些相关问题:

网络资源:

【问题讨论】:

    标签: javascript jquery performance memory-leaks garbage-collection


    【解决方案1】:

    据我了解,javascript 中的内存管理是通过引用计数来完成的——虽然对对象的引用仍然存在,但它不会被释放。这意味着在单页应用程序中创建内存泄漏是微不足道的,并且可能会绊倒那些来自 java 背景的用户。这不是特定于 JQuery。以如下代码为例:

    function MyObject = function(){
       var _this = this;
       this.count = 0;
       this.getAndIncrement = function(){
           _this.count++;
           return _this.count;
       }
    }
    
    for(var i = 0; i < 10000; i++){
        var obj = new MyObject();
        obj.getAndIncrement();
    }
    

    在您查看内存使用情况之前,它看起来很正常。由于“_this”指针(增加 i 的最大值以更显着地看到它),当页面处于活动状态时,MyObject 的实例永远不会被释放。 (在旧版本的 IE 中,它们在程序退出之前永远不会被释放。)由于 javascript 对象可能在框架之间共享(我不建议尝试这样做,因为它非常容易受到影响。),即使在现代浏览器中,也有一些情况下 javascript物体的停留时间可能比它们预期的要长。

    在jquery的上下文中,经常会存储引用来节省dom搜索的开销——例如:

    function run(){
        var domObjects = $(".myClass");
        domObjects.click(function(){
            domObjects.addClass(".myOtherClass");
        });
    }
    

    由于回调函数中对它的引用,这段代码将永远保留 domObject(及其所有内容)。

    如果 jquery 的编写者在内部错过了这样的实例,那么库本身就会泄漏,但更多的时候是客户端代码。

    第二个例子可以通过在不再需要时显式清除指针来修复:

    function run(){
        var domObjects = $(".myClass");
        domObjects.click(function(){
            if(domObjects){
                domObjects.addClass(".myOtherClass");
                domObjects = null;
            }
        });
    }
    

    或再次查找:

    function run(){
        $(".myClass").click(function(){
            $(".myClass").addClass(".myOtherClass");
        });
    }
    

    一个好的经验法则是在定义回调函数时要小心,并尽可能避免过多的嵌套。

    编辑:正如 Erik 在 cmets 中指出的那样,您还可以使用 this 指针来避免不必要的 dom 查找:

    function run(){
        $(".myClass").click(function(){
            $(this).addClass(".myOtherClass");
        });
    }
    

    【讨论】:

    • 这是一个示例:jsfiddle.net/qTu6y/8 您可以在分析器中使用 Chrome 的“Take a heapshot”来查看该代码块的每次运行都会消耗大约 20MB 的 RAM。 (在测试它时,我确实遇到了“脚本在 chrome 上使用了太多 RAM 错误”)
    • $(this).addClass 在最后一种情况下会更好地提高性能,因为“this”表示核心 JS DOM 元素集合(这是 JQuery 对象通常使用适配器样式模式包装的内容)在
    • jsfiddle.net/qTu6y/8 不是内存泄漏。这是在同一个对象上创建 100000 个不同的单击处理程序实例的示例。当然,它们会被保留,因为它们仍然是活动的点击处理程序。如果您不相信我,请在单击处理程序中添加警报,运行代码,单击 div,然后查看单击一次警报出现的次数。
    • 原来的“MyObject”例子也是错误的。它不会造成内存泄漏。至少在 Chrome 等现代浏览器中不会。现代 javascript 引擎不单独使用引用计数来进行垃圾收集。只要您中断对根垃圾收集器对象之一的引用,您的对象就会被删除。对于纯 javascript 对象,这仅意味着确保没有指向返回全局范围的对象的引用链。当你开始混合 DOM 对象时,它会变得有点复杂。
    • 关于“MyObject”示例 - 它肯定在 IE6 中泄漏(即使在离开页面后),它在 IE7 中泄漏,并且它曾经在 chrome 中泄漏。我很高兴浏览器正在发展到不再是这样的地步
    【解决方案2】:

    我将在这里贡献一个反模式,即“中间链引用”泄漏。

    jQuery 的优势之一是它的链式 API,它允许您继续更改、过滤和操作元素:

    $(".message").addClass("unread").find(".author").addClass("noob");
    

    在该链的末尾,您有一个包含所有“.message .author”元素的 jQuery 对象,但 那个 对象引用了原始“.message”元素的对象。您可以通过.end() 方法找到他们并对他们做一些事情:

     $(".message")
       .find(".author")
         .addClass("prolific")
       .end()
       .addClass("unread");
    

    现在以这种方式使用时不会出现泄漏问题。但是,如果您将链的结果分配给具有较长生命周期的变量,则对早期集合的反向引用会保留下来,并且在变量超出范围之前不能被垃圾收集。如果该变量是全局变量,则引用永远不会超出范围。

    例如,假设您在 2008 年的一些博客文章中读到 $("a").find("b")$("a b")“更有效”(即使它甚至不值得考虑这样的微优化)。您决定需要一个页面范围的全局来保存作者列表,因此您可以这样做:

    authors = $(".message").find(".author");
    

    现在您确实有一个带有作者列表的 jQuery 对象,但它还引用了一个 jQuery 对象,即完整的消息列表。您可能永远不会使用它,甚至不知道它的存在,而且它会占用内存。

    请注意,泄漏只能通过从现有集合中选择 元素的方法发生,例如 .find.filter.children 等。文档指出当 new 集被返回。如果链具有简单的非过滤方法,例如.css,那么简单地使用链式 API 不会导致泄漏,所以这没关系:

    authors = $(".message .author").addClass("prolific");
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-22
      • 2023-03-08
      • 2011-01-14
      • 1970-01-01
      • 1970-01-01
      • 2021-09-16
      • 2015-08-06
      • 1970-01-01
      相关资源
      最近更新 更多