jQuery内存泄漏模式和原因

时间:2021-10-13 19:01:20

What are some of the standard issues or coding patterns in jQuery which lead to memory leaks?

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


I have seen a number of questions related to the ajax() call or jsonp or DOM removal on *. Most of the jQuery memory leak questions are focussed on specific issues or browsers and it would be nice to have a listing of the standard memory leak patterns in jQuery.

我在*上看到了一些与ajax()调用或jsonp或DOM删除相关的问题。大多数jQuery内存泄漏问题都集中在特定问题或浏览器上,在jQuery中列出标准内存泄漏模式会更好。

Here are some related questions on SO:

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

Resources on the web:

网上资源:

2 个解决方案

#1


28  

From what I understand, memory management in javascript is accomplished by reference counting - while a reference to an object still exists, it will not be deallocated. This means that creating a memory leak in a single page application is trivial, and can trip up those of use coming from a java background. This is not specific to JQuery. Take the following code for example:

根据我的理解,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();
}

It will look normal until you look at memory usage. Instances of MyObject are never deallocated while the page is active, due to the "_this" pointer (increase the max value of i to see it more dramatically.). (In older versions of IE they were never deallocated until the program exits.) Since javascript objects may be shared between frames (I don't recommend trying this as it is seriously temperamental.), there are cases where even in a modern browser javascript objects can hang around a lot longer than they are meant to.

在您查看内存使用情况之前,它看起来很正常。由于“_this”指针(增加i的最大值以使其更加显着),因此在页面处于活动状态时,MyObject的实例永远不会被释放。 (在旧版本的IE中,它们从未被释放,直到程序退出。)由于javascript对象可能在帧之间共享(我不建议尝试这个,因为它是严重的气质。),有些情况甚至在现代浏览器中javascript物体可以比它们的意图长得多。

In the context of jquery, references are often stored to save the overhead of dom searching - for example:

在jquery的上下文中,通常存储引用以节省dom搜索的开销 - 例如:

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

This code will hold on to domObject (and all its contents) forever, because of the reference to it in the callback function.

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

If the writers of jquery have missed instances like this internally, then the library itself will leak, but more often it is the client code.

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

The second example can be fixed by explicitly clearing the pointer when it is no longer required:

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

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

or doing the lookup again:

或者再次进行查找:

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

A good rule of thumb is to be careful where you define your callback functions, and avoid too much nesting where possible.

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

Edit: As was pointed out in the comments by Erik, you could also use the this pointer to avoid the unnescessary dom lookup:

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

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

#2


15  

I'll contribute one anti-pattern here, which is the "mid-chain reference" leak.

我将在这里贡献一个反模式,这是“中链参考”泄漏。

One of jQuery's strengths is its chaining API, which lets you continue to change, filter, and manipulate the elements:

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

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

At the end of that chain you have a jQuery object with all the ".message .author" elements, but that object refers back to and object with the original ".message" elements. You can get to them via the .end() method and do something to them:

在该链的末尾,您有一个带有所有“.message .author”元素的jQuery对象,但该对象返回并使用原始的“.message”元素进行对象。你可以通过.end()方法找到它们并对它们做些什么:

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

Now when used this way there are no problems with leaks. However, if you assign the result of a chain to a variable that has a long life, the back-references to earlier sets stay around and cannot be garbage collected until the variable goes out of scope. If that variable is global, the references never go out of scope.

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

So for example, let's say you read on some 2008 blog post that $("a").find("b") was "more efficient" than $("a b") (even though its not worthy of even thinking about such a micro-optimization). You decide you need a page-wide global to hold a list of authors so you do this:

例如,假设您在2008年的一篇博客文章中读到$(“a”)。find(“b”)比$(“ab”)“更有效”(尽管它甚至不值得考虑这样做)微观优化)。您决定需要一个页面范围的全局来保存作者列表,以便您执行此操作:

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

Now you do have a jQuery object with the list of authors, but it also refers back to a jQuery object that is the full list of messages. You probably will never use it or even know it's there, and it's taking up memory.

现在你有了一个带有作者列表的jQuery对象,但它也引用了一个jQuery对象,它是完整的消息列表。你可能永远不会使用它,甚至不知道它在那里,它占用了内存。

Note that leaks can only occur with the methods that select new elements from an existing set, such as .find, .filter, .children etc. The docs indicate when a new set is returned. Simply using a chaining API doesn't cause a leak if the chain has simple non-filtering methods like .css, so this is okay:

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

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

#1


28  

From what I understand, memory management in javascript is accomplished by reference counting - while a reference to an object still exists, it will not be deallocated. This means that creating a memory leak in a single page application is trivial, and can trip up those of use coming from a java background. This is not specific to JQuery. Take the following code for example:

根据我的理解,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();
}

It will look normal until you look at memory usage. Instances of MyObject are never deallocated while the page is active, due to the "_this" pointer (increase the max value of i to see it more dramatically.). (In older versions of IE they were never deallocated until the program exits.) Since javascript objects may be shared between frames (I don't recommend trying this as it is seriously temperamental.), there are cases where even in a modern browser javascript objects can hang around a lot longer than they are meant to.

在您查看内存使用情况之前,它看起来很正常。由于“_this”指针(增加i的最大值以使其更加显着),因此在页面处于活动状态时,MyObject的实例永远不会被释放。 (在旧版本的IE中,它们从未被释放,直到程序退出。)由于javascript对象可能在帧之间共享(我不建议尝试这个,因为它是严重的气质。),有些情况甚至在现代浏览器中javascript物体可以比它们的意图长得多。

In the context of jquery, references are often stored to save the overhead of dom searching - for example:

在jquery的上下文中,通常存储引用以节省dom搜索的开销 - 例如:

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

This code will hold on to domObject (and all its contents) forever, because of the reference to it in the callback function.

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

If the writers of jquery have missed instances like this internally, then the library itself will leak, but more often it is the client code.

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

The second example can be fixed by explicitly clearing the pointer when it is no longer required:

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

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

or doing the lookup again:

或者再次进行查找:

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

A good rule of thumb is to be careful where you define your callback functions, and avoid too much nesting where possible.

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

Edit: As was pointed out in the comments by Erik, you could also use the this pointer to avoid the unnescessary dom lookup:

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

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

#2


15  

I'll contribute one anti-pattern here, which is the "mid-chain reference" leak.

我将在这里贡献一个反模式,这是“中链参考”泄漏。

One of jQuery's strengths is its chaining API, which lets you continue to change, filter, and manipulate the elements:

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

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

At the end of that chain you have a jQuery object with all the ".message .author" elements, but that object refers back to and object with the original ".message" elements. You can get to them via the .end() method and do something to them:

在该链的末尾,您有一个带有所有“.message .author”元素的jQuery对象,但该对象返回并使用原始的“.message”元素进行对象。你可以通过.end()方法找到它们并对它们做些什么:

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

Now when used this way there are no problems with leaks. However, if you assign the result of a chain to a variable that has a long life, the back-references to earlier sets stay around and cannot be garbage collected until the variable goes out of scope. If that variable is global, the references never go out of scope.

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

So for example, let's say you read on some 2008 blog post that $("a").find("b") was "more efficient" than $("a b") (even though its not worthy of even thinking about such a micro-optimization). You decide you need a page-wide global to hold a list of authors so you do this:

例如,假设您在2008年的一篇博客文章中读到$(“a”)。find(“b”)比$(“ab”)“更有效”(尽管它甚至不值得考虑这样做)微观优化)。您决定需要一个页面范围的全局来保存作者列表,以便您执行此操作:

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

Now you do have a jQuery object with the list of authors, but it also refers back to a jQuery object that is the full list of messages. You probably will never use it or even know it's there, and it's taking up memory.

现在你有了一个带有作者列表的jQuery对象,但它也引用了一个jQuery对象,它是完整的消息列表。你可能永远不会使用它,甚至不知道它在那里,它占用了内存。

Note that leaks can only occur with the methods that select new elements from an existing set, such as .find, .filter, .children etc. The docs indicate when a new set is returned. Simply using a chaining API doesn't cause a leak if the chain has simple non-filtering methods like .css, so this is okay:

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

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