web-dev-qa-db-ja.com

DOM削除によるjQueryメモリリーク

これは、jQueryを使用してIE8でメモリリークが発生する非常にシンプルなWebページです(Windowsタスクマネージャでiexplore.exeプロセスのメモリ使用量が時間とともに増加するのを監視することでメモリリークを検出します)。

_<html>
<head>
    <title>Test Page</title>
    <script type="text/javascript" src="jquery.js"></script>
</head>
<body>
<script type="text/javascript">
    function resetContent() {
        $("#content div").remove();
        for(var i=0; i<10000; i++) {
            $("#content").append("<div>Hello World!</div>");
        }
        setTimeout(resetTable, 2000);
    }
    $(resetContent);
</script>
<div id="content"></div>
</body>
</html>
_

どうやらjQuery.remove()関数を呼び出しても、メモリリークが発生します。次のように、メモリリークが発生しない独自の削除関数を作成できます。

_$.fn.removeWithoutLeaking = function() {
    this.each(function(i,e){
        if( e.parentNode )
            e.parentNode.removeChild(e);
    });
};
_

これはうまく機能し、メモリをリークしません。それでは、なぜjQueryはメモリをリークするのでしょうか? jQuery.remove()に基づいて別の削除関数を作成しましたが、実際にリークが発生します。

_$.fn.removeWithLeakage = function() {
    this.each(function(i,e) {
        $("*", e).add([e]).each(function(){
            $.event.remove(this);
            $.removeData(this);
        });
        if (e.parentNode)
            e.parentNode.removeChild(e);
    });
};
_

興味深いことに、メモリリークは、削除されるDOM要素に関連するイベントやデータからのメモリリークを防ぐためにjQueryに含まれる各呼び出しによって引き起こされるようです。 removeWithoutLeaking関数を呼び出すと、メモリは時間とともに一定になりますが、代わりにremoveWithLeakageを呼び出すと、成長し続けます。

私の質問は、各呼び出しについてはどうですか

_$("*", e).add([e]).each(function(){
    $.event.remove(this);
    $.removeData(this);
});
_

おそらくメモリリークを引き起こしている可能性がありますか?

編集:コードのタイプミスを修正しました。再テストの結果、結果に影響がないことが判明しました。

さらに編集:jQueryプロジェクトにバグレポートを提出しました。これはjQueryのバグのようです。 http://dev.jquery.com/ticket/5285

46
Eli Courtwright

私はDavidがremoveChildリークの疑いがあるものに遭遇するかもしれないと思ったが、IE8でそれを再現することはできない。 divを手動で削除すると、リークは発生しません。 removeChildの代わりに_outerHTML= ''_(またはmove-to-binに続いてbin.innerHTML)を使用するようにjQueryを変更すると、依然としてリークが発生します。

削除の過程で、jQueryのremoveのビットをハッキングし始めました。 1.3.2の1244行目:

_//jQuery.event.remove(this);
jQuery.removeData(this);
_

その行をコメントアウトすると、リークは発生しませんでした。

したがって、event.removeを見てみましょう。data('events')を呼び出して、要素にイベントがアタッチされているかどうかを確認します。 dataは何をしていますか?

_// Compute a unique ID for the element
if ( !id )
    id = elem[ expando ] = ++uuid;
_

ああ。つまり、jQueryのuuid-to-data-lookupエントリハックプロパティの1つを追加しようとしていますが、すべての要素がread削除する要素の!なんて馬鹿げている。この行をその直前に置くことで、それを短絡できます。

_// Don't create ID/lookup if we're only reading non-present data
if (!id && data===undefined)
    return undefined;
_

iE8でこのケースのリークを修正するようです。迷路の中でjQueryである他のものを壊さないことを保証することはできませんが、論理的には理にかなっています。

私が解決できる限り、リークは単に_jQuery.cache_オブジェクト(データストアであり、実際にはキャッシュそのものではありません)が削除された要素ごとに新しいキーが追加されるにつれてますます大きくなっています。 removeDataはこれらのキャッシュエントリを削除する必要がありますが、オブジェクトからキーをdeleteすると、IEはスペースを回復しないようです。

(いずれにせよ、これは私が評価していないjQueryの動作の例です。些細な単純な操作であるべきであるために、ボンネットの下であまりにも多くのことをしています...いくつかはかなり疑わしいものです。全体expandoと IEで属性として表示されることを防ぐためにjQueryが正規表現を介してinnerHTMLに対して行うこと が壊れてandいだけです。ゲッターとセッターを同じにする習慣関数は混乱し、ここではバグになります。)

[奇妙なことに、メモリリークが実際になくなる前にjquery.jsでリークテストを長期間放置すると、完全に誤ったエラーが発生することがありました...「予期しないコマンド」のようなものがあり、「nodeNameがnullかどうか」 667行目のオブジェクト」は、nodeNameがnullであるかどうかのチェックがあることは言うまでもなく、私が見る限り実行されていないはずです! IEはここであまり自信を与えてくれない...]

58
bobince

JQuery 1.5(23. 2月バージョン)で修正されるようです。私は1.4.2で同じ問題に遭遇し、最初に上記のようにdomを削除して修正し、次に新しいバージョンを試しました。

5
Ben

要素の削除は、固有のDOM問題です。これは私たちと一緒にいるつもりです。同上。

jQuery.fn.flush = function()
/// <summary>
/// $().flush() re-makes the current element stack inside $() 
/// thus flushing-out the non-referenced elements
/// left inside after numerous remove's, append's etc ...
/// </summary>
{ return jQuery(this.context).find(this.selector); }

JQをハッキングする代わりに、この拡張機能を使用します。特にremoves()とclones()が多いページでは:

$exact = $("whatever").append("complex html").remove().flush().clone();

また、次のものも役立ちます:

// remove all event bindings , 
// and the jQ data made for jQ event handling
jQuery.unbindall = function () { jQuery('*').unbind(); }
//
$(document).unload(function() { 
  jQuery.unbindall();
});
4
user182669

http://docs.jquery.com/JQuery_1.4_Roadmap のjQuery 1.4ロードマップを参照してください。具体的には、「。remove()後の.outerHTMLを使用してクリーンアップする」セクションでは、IE remove関数が呼び出されるために発生するメモリリークの問題を扱います。

おそらくあなたの問題は次のリリースで解決されるでしょう。

2
David Andres

JQuery 1.4.1には次のものがあります。

    cleanData: function (elems) {
        for (var i = 0, elem, id; (elem = elems[i]) != null; i++) {
            jQuery.event.remove(elem);
            jQuery.removeData(elem);
        }
    }

リークの問題を解消するために修正しなければならなかったものは次のとおりです。

    cleanData: function (elems) {
        for (var i = 0, elem, id; (elem = elems[i]) != null; i++) {
            jQuery.event.remove(elem);
            jQuery.removeData(elem);
            jQuery.purge(elem);
        }
    }

追加機能:

    purge: function (d) {
        var a = d.childNodes;
        if (a) {
            var remove = false;
            while (!remove) {
                var l = a.length;
                for (i = 0; i < l; i += 1) {
                    var child = a[i];
                    if (child.childNodes.length == 0) {
                        jQuery.event.remove(child);
                        d.removeChild(child);
                        remove = true;
                        break;
                    }
                    else {
                        jQuery.purge(child);
                    }
                }
                if (remove) {
                    remove = false;
                } else {
                    break;
                }
            }
        }
    },
1
Basil

emptyの代わりにremoveを呼び出すと、リークしますか?

$("#content").empty();
1
Josh Stodola