2011-02-04 5 views
11

次のコードがリークするのはなぜですか?Internet Explorer 8でこのリークが発生するのはなぜですか?

for (var i = 0; i < 100; i++) { 
    var item = {}; 
    item.elem = document.createElement('div'); 
    document.body.appendChild(item.elem); 
    item.addEvent = function(name,listener) { 
     var self = this; 
     var wrappedListener = function() { 
      return listener.apply(self,arguments); 
     } 
     //Uh-oh creating a circular reference here! 
     //The wrappedListener has a closure on self and therefore on item.elem. 
     addEvent(this.elem,name,wrappedListener); 
     return wrappedListener; 
    } 
    var wrap = item.addEvent('eventName',listen); 

    //Now remove the eventHandler - this should free up the circular reference. 
    removeEvent(item.elem, 'eventName', wrap); 
    if (item.elem.parentNode) { 
     item.elem.parentNode.removeChild(item.elem); 
    } 
    //item.elem = null; //With this also un-commented, the leak disappears. 
    //The fact that I have to null item.elem tells me that something is holding 
    //a reference to item, and therefore elem. Setting elem to null fixes the 
    //problem, but since I am removing the event handler, I don't think this 
    //should be required. 
} 

注:addEventremoveEventはちょうど、Internet Explorerと他のブラウザとの間に抽象attachEvent/addEventListenerの違いにあります。

私はこの問題を示すjsFiddleプロジェクトを作成しました。 Internet Explorer 8を起動して、タスクマネージャーまたはプロセスエクスプローラーで見ることができます。また、addEventremoveEventの定義が表示されます。

http://jsfiddle.net/rJ8x5/34/

EDIT:まあ、私は、次の解決策を考え出しました。それはかなりではありませんが、それは動作します! http://jsfiddle.net/rJ8x5/43/

var item = {}; 
item.elem = document.createElement('div'); 
document.body.appendChild(item.elem); 
item.addEvent = function(name,listener) { 
    var wrappedListener = function() { 
     //Access the scope through the callee properties. 
     return listener.apply(arguments.callee.scope, arguments); 
    } 
    addEvent(this.elem,name,wrappedListener); 
    //Save the scope not as a closure, but as a property on the handler. 
    wrappedListener.scope = this 
    return wrappedListener; 
} 
var wrap = item.addEvent('eventName',listen); 
removeEvent(item.elem, 'eventName', wrap); 
//Force the circular reference to GO AWAY. 
wrap.scope = null 
if (item.elem.parentNode) { 
    item.elem.parentNode.removeChild(item.elem); 
} 
//item.elem = null; //No longer needed. 
+0

私はIEがネイティブオブジェクトとjavascriptオブジェクトに対して別々のガベージコレクションを使用するバグを持っていたので、JavaScriptオブジェクトとネイティブオブジェクトの間のサイクルを形成するときにガベージコレクタはそれをクリーンアップできません。それはそれかもしれませんか?私はitemがアイテムを参照するハンドラを持つdivであるitem.elemを指していると考えています。 – btilly

+0

- 本当にあなたの質問には関係ありませんが、代わりに 'listener.apply(item、arguments)'を使って 'self'変数を削除することができます。 :-) –

+0

完全に!しかし、これは非常に単純です。私は、addEventメソッドがインスタンスへのアクセスを便利に持っていない場所を書きました。したがって、var self = this – jordancpaul

答えて

5

問題が(ほぼ常に、Internet Explorerで、BTW)イベントです。

http://jsfiddle.net/rJ8x5/39/をご覧になり、ごみがどのように細かく収集されているかを確認してください。

イベントを添付するときに循環参照を作成しています。詳細についてはCircular references to DOM objects on an HTML page cause a memory leakでお読みください。

+0

ああ!はい。関数がクロージャー内のDOM要素への参照をトラップするため、JScriptによってガベージコレクションされるときにDOM要素がリークします。そうですか? – Pointy

+0

@Pointy:はい、それはイベントハンドラのスコープに関するものであり、そのスコープにトラップされたDOM参照に関するものです。 –

+0

私はIEの循環参照に関連するメモリリークについて認識しています。しかし、ここでのポイントは、私は特にイベントハンドラを削除していることです。これが循環参照を破るべきではないか? – jordancpaul

1

attachEventが不適切に呼び出されているためコードがリークします。Microsoftのdocumentationは、イベント名が標準のDHTMLイベントであると主張しています。

'eventName'をに変更すると、漏れません。

より確実な解決方法は、イベント添付コードを'on'+eventname in domElementにチェックするように変更し、これが偽である場合にイベントを添付することを拒否することです。

関連する問題