Утечка памяти в jQuery с удалением DOM - PullRequest
45 голосов
/ 23 сентября 2009

Вот очень простая веб-страница, на которой происходит утечка памяти в IE8 с помощью jQuery (я обнаруживаю утечки памяти, наблюдая, как со временем в диспетчере задач 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);
    });
};

Интересно, что утечка памяти, по-видимому, вызвана каждым вызовом, который включает jQuery для предотвращения утечек памяти из-за событий и данных, связанных с удаляемыми элементами DOM. Когда я вызываю функцию removeWithoutLeaking, моя память остается постоянной с течением времени, но когда я вместо этого вызываю removeWithLeakage, она просто продолжает расти.

У меня вопрос, а как насчет того, что каждый звонок

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

может быть причиной утечки памяти?

EDIT: исправлена ​​опечатка в коде, которая после повторного тестирования не оказала влияния на результаты.

ДОПОЛНИТЕЛЬНОЕ РЕДАКТИРОВАНИЕ: Я отправил отчет об ошибке в проект jQuery, поскольку это, похоже, ошибка jQuery: http://dev.jquery.com/ticket/5285

Ответы [ 6 ]

57 голосов
/ 23 сентября 2009

Я думал, что Дэвид может быть на что-то с предполагаемой утечкой removeChild, но я не могу воспроизвести это в IE8 ... это может случиться в более ранних браузерах, но это не то, что мы имеем здесь. Если я удаляю вручную div'ы, утечки нет; если я изменю jQuery, чтобы использовать outerHTML= '' (или move-to-bin, а затем bin.innerHTML) вместо removeChild, все равно будет утечка.

В процессе исключения я начал взламывать биты remove в jQuery. строка 1244 в 1.3.2:

//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;

О. Таким образом, он добавляет одно из свойств взлома записи uUid-to-data-lookup jQuery для каждого элемента, который он даже пытается прочитать данных, включая каждого потомка удаляемого элемента! Как глупо. Я могу замкнуть это, поместив эту строку прямо перед ней:

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

, который, по-видимому, исправляет утечку для этого случая в IE8. Не могу гарантировать, что это не сломает что-то еще в лабиринте, который является jQuery, но логически это имеет смысл.

Насколько я могу понять, утечка - это просто объект jQuery.cache (который является хранилищем данных, а не кешем как таковой), который становится все больше и больше по мере добавления нового ключа для каждого удаленного элемента. Несмотря на то, что removeData должен удалить эти записи в кеше, ОК, похоже, IE не восстанавливает пространство при delete ключе от Object.

(В любом случае, это пример такого поведения jQuery, которое я не ценю. Он делает слишком много под капотом для того, что должно быть тривиально простой операцией ... некоторые из которых довольно сомнительны Все, что происходит с расширением и , что jQuery делает с innerHTML через регулярное выражение, чтобы предотвратить отображение в качестве атрибута в IE , просто сломано и уродливо. И привычка делать метод получения и установки одинаковой функцией сбивает с толку и здесь приводит к ошибке.)

[Странно, но проверка на утечку в течение длительных периодов времени иногда приводила к совершенно ложным ошибкам в jquery.js до того, как память фактически закончилась ... было что-то вроде «неожиданной команды», и я заметил, что «имя_узла null или нет объекта 'в строке 667, который, насколько я вижу, даже не должен был выполняться, не говоря уже о том, что там проверяется, что nodeName имеет значение null! IE не дает мне много уверенности здесь ...]

5 голосов
/ 24 февраля 2011

Кажется исправленным в jQuery 1.5 (версия от 23 февраля). Я столкнулся с той же проблемой с 1.4.2 и исправил ее сначала с удалением dom, как описано выше, а затем попробовал новую версию.

4 голосов
/ 29 сентября 2009

Удаление элемента является неотъемлемой проблемой 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 я использую это расширение. Особенно на страницах с большим количеством удалений () и клонов ():

$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();
});
2 голосов
/ 23 сентября 2009

См. Дорожную карту jQuery 1.4 по адресу http://docs.jquery.com/JQuery_1.4_Roadmap. В частности, раздел «Использование .outerHTML для очистки после .remove ()» посвящен проблемам утечки памяти, возникающим в IE из-за вызываемой функции удаления.

Возможно, ваши проблемы будут решены в следующем выпуске.

1 голос
/ 12 декабря 2012

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 голос
/ 23 сентября 2009

Происходит ли утечка, если вы звоните empty вместо remove?

$("#content").empty();
...