web-dev-qa-db-ja.com

DOM要素が削除された場合、そのリスナーもメモリから削除されますか?

DOM要素が削除された場合、そのリスナーもメモリから削除されますか?

291
nimrod

現代のブラウザ

普通のJavaScript

削除されたDOM要素が参照を含まない(参照を指していない)場合、yes - 要素自体は、ガベージコレクタによって次のように取得されます。それに関連付けられているすべてのイベントハンドラ/リスナ.

var a = document.createElement('div');
var b = document.createElement('p');
// Add event listeners to b etc...
a.appendChild(b);
a.removeChild(b);
b = null; 
// A reference to 'b' no longer exists 
// Therefore the element and any event listeners attached to it are removed.

しかしながら;それでもその要素を指す参照がある場合、その要素とそのイベントリスナーはメモリに保持されます。

var a = document.createElement('div');
var b = document.createElement('p'); 
// Add event listeners to b etc...
a.appendChild(b);
a.removeChild(b); 
// A reference to 'b' still exists 
// Therefore the element and any associated event listeners are still retained.

jQuery

JQueryの関連メソッド(remove()など)もまったく同じように機能すると想定してもかまいません(remove()removeChild()を使用して記述されていることを考えると)。

しかし、これは正しくありません。 jQueryライブラリは実際にはcleanData()これはこのメソッドのようなものです と呼ばれる内部メソッド(文書化されておらず、理論上いつでも変更することができます)を持っています。 DOMから削除されたときの要素(これはvia。remove()empty()html("")などで)。


古いブラウザ

古いブラウザ、特にIEの古いバージョンでは、イベントリスナがそれらがアタッチされていた要素への参照を保持し続けているためにメモリリークの問題があることが知られています。

レガシーIEバージョンのメモリリークを修正するために使用された原因、パターン、および解決策について、より詳細な説明が必要な場合は、 こちらをお読みください。 MSDNのInternet Explorerのリークパターンの理解と解決に関する記事

これに関連する記事がさらにいくつかあります。

この場合、リスナーを自分で手動で削除するのがおそらく賢い習慣です(メモリがアプリケーションにとって非常に重要であり、実際にそのようなブラウザをターゲットにしている場合のみ)。

263
lifetimes

jQueryに関して:

.remove()メソッドはDOMから要素を取り出します。要素自体とその中のすべての要素を削除したい場合は、.remove()を使用してください。要素自体に加えて、要素に関連付けられているすべてのバインド済みイベントとjQueryデータが削除されます。データやイベントを削除せずに要素を削除するには、代わりに.detach()を使用してください。

参照先: http://api.jquery.com/remove/

jQuery v1.8.2 .remove()ソースコード:

remove: function( selector, keepData ) {
    var elem,
        i = 0;

    for ( ; (elem = this[i]) != null; i++ ) {
        if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
            if ( !keepData && elem.nodeType === 1 ) {
                jQuery.cleanData( elem.getElementsByTagName("*") );
                jQuery.cleanData( [ elem ] );
            }

            if ( elem.parentNode ) {
                elem.parentNode.removeChild( elem );
            }
        }
    }

    return this;
}

どうやらjQueryはnode.removeChild()を使っています

これによると: https://developer.mozilla.org/en-US/docs/DOM/Node.removeChild

The removed child node still exists in memory, but is no longer part of the DOM. You may reuse the removed node later in your code, via the oldChild object reference.

つまり、イベントリスナは削除されるかもしれませんが、nodeはまだメモリに存在します。

21
Sreenath S

イベントハンドラーがクロージャーを使用して要素への参照を保持し、要素がイベントハンドラーへの参照を保持している場合に、メモリリークが発生しないようにヒープを監視することを躊躇しないでください。

ガベージコレクタは循環参照を好まない。

通常のメモリリークの場合:オブジェクトに要素への参照があることを認めます。その要素にはハンドラへの参照があります。そしてハンドラはオブジェクトへの参照を持ちます。オブジェクトは他の多くのオブジェクトを参照しています。このオブジェクトは、コレクションからそれを参照しないことで捨てたと思うコレクションの一部です。 =>オブジェクト全体とそれが参照するすべてのものは、ページが終了するまでメモリに残ります。 =>あなたはあなたのオブジェクトクラスのための完全なkillメソッドについて考えるか、例えばmvcフレームワークを信頼しなければなりません。

さらに、Chrome開発ツールの保持ツリー部分を使用することを躊躇しないでください。

7
lib3d

他の答えを拡張するだけで...

デリゲートイベントハンドラは要素の削除時に削除されません。

$('body').on('click', '#someEl', function (event){
  console.log(event);
});

$('#someEL').remove(); // removing the element from DOM

今チェック:

$._data(document.body, 'events');
7
Lucky Soni

jQueryに関しては、次の一般的な方法で、データやイベントハンドラなどの他の構成要素も削除されます。

remove()

要素自体に加えて、要素に関連付けられているすべてのバインド済みイベントとjQueryデータが削除されます。

empty()

メモリリークを回避するために、jQueryは、要素自体を削除する前に、子要素からデータやイベントハンドラなどの他の構成要素を削除します。

html()

さらに、jQueryは、子要素からデータやイベントハンドラなどの他の構成要素を削除してから、それらの要素を新しいコンテンツに置き換えます。

6
Jaskey

はい、ガベージコレクタもそれらを削除します。ただし、従来のブラウザでは必ずしもそうとは限りません。

2
Darin Dimitrov