web-dev-qa-db-ja.com

jQueryのメモリリークのパターンと原因

メモリリークにつながるjQueryの標準的な問題またはコーディングパターンにはどのようなものがありますか?


StackOverflowでのajax()呼び出し、jsonpまたはDOMの削除に関連する多くの質問を見てきました。 jQueryのメモリリークに関する質問のほとんどは、特定の問題やブラウザに焦点を当てているため、jQueryの標準的なメモリリークパターンのリストを用意しておくと便利です。

SOに関するいくつかの関連質問を次に示します。

Web上のリソース:

44
Thimmayya

私が理解していることから、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();
}

メモリ使用量を確認するまで、正常に表示されます。 MyObjectのインスタンスは、ページがアクティブな間は "_this"ポインターが原因で割り当て解除されることはありません(iの最大値を大きくすると、より劇的に表示されます)。 (IEの古いバージョンでは、プログラムが終了するまで割り当てが解除されませんでした。)JavaScriptオブジェクトはフレーム間で共有される可能性があるため(これは非常に気質的であるため、試さないでください)。最近のブラウザでも、JavaScriptオブジェクトが意図したよりもずっと長い間ぶらつく可能性があります。

Jqueryのコンテキストでは、dom検索のオーバーヘッドを節約するために、参照が保存されることがよくあります。次に例を示します。

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

このコードは、コールバック関数での参照のため、domObject(およびそのすべてのコンテンツ)を永久に保持します。

Jqueryの作成者がこのようなインスタンスを内部で見逃した場合、ライブラリ自体がリークしますが、多くの場合それはクライアントコードです。

2番目の例は、ポインタが不要になったときに明示的にクリアすることで修正できます。

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

またはもう一度ルックアップを行います:

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

経験則として、コールバック関数を定義する場所には注意し、可能な限りネストしすぎないようにしてください。

編集:Erikのコメントで指摘されているように、thisポインターを使用して不要なdom検索を回避することもできます。

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

ここでは、「ミッドチェーン参照」リークであるアンチパターンを1つ提供します。

JQueryの強みの1つは、チェーンAPIです。これにより、要素の変更、フィルター、操作を継続できます。

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

そのチェーンの最後には、すべての「.message .author」要素を持つjQueryオブジェクトがありますが、thatオブジェクトは、元の「.message」要素を持つオブジェクトを参照します。 .end()メソッドを介してそれらにアクセスし、それらに何かを行うことができます。

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

この方法で使用すると、リークの問題はありません。ただし、チェーンの結果を寿命の長い変数に割り当てると、以前のセットへの後方参照はそのまま残り、変数がスコープ外になるまでガベージコレクションできません。その変数がグローバルである場合、参照が範囲外になることはありません。

たとえば、$("a").find("b")$("a b")よりも「効率的」だったとする2008年のブログ投稿を読んだとします(このようなマイクロ最適化についてさえ考える価値はありませんが)。 。これを行うには、著者のリストを保持するためにページ全体のグローバルが必要であると判断します。

_authors = $(".message").find(".author");
_

これで、作成者のリストを含むjQueryオブジェクトが作成されましたが、メッセージの完全なリストであるjQueryオブジェクトも参照しています。あなたはおそらくそれを決して使用しないでしょう、またはそれがそこにあることさえ知っているでしょう、そしてそれはメモリを占有しています。

リークは、_.find_、_.filter_、_.children_など、既存のセットからnew要素を選択するメソッドでのみ発生することに注意してください。ドキュメントでは、 newセットが返されます。チェーンに_.css_のような単純な非フィルタリングメソッドがある場合、チェーンAPIを使用するだけではリークは発生しないため、これで問題ありません。

_authors = $(".message .author").addClass("prolific");
_
16
Dave Methvin