Почему эта утечка в 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.
}

Примечание: addEvent и removeEvent предназначены для абстрактного различия attachEvent/addEventListener между Internet Explorer и другими браузерами.

Я создал проект jsFiddle, который демонстрирует проблему. Просто запустите Internet Explorer 8 и посмотрите, как это происходит в диспетчере задач или в Process Explorer. Кроме того, вы увидите определение addEvent и removeEvent там.

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.

Ответ 1

Проблема заключается в событиях (как почти всегда в Internet Explorer, BTW).

Посмотрите http://jsfiddle.net/rJ8x5/39/ и обратите внимание на то, как этот мусор собирает штрафы.

Вы создаете круговые ссылки при прикреплении событий. Подробнее об этом в Циркулярные ссылки на объекты DOM на HTML-странице вызывают утечку памяти.

Ответ 2

Код течет, потому что вы неправильно вызываете attachEvent - Microsoft документация настаивает на том, что имя события является стандартным событием DHTML.

Если вы меняете 'eventName' на 'click', он не течет.

Более надежным решением было бы изменить код присоединения к событию, чтобы проверить 'on'+eventname in domElement и отказаться от присоединения события, если оно ложно.