Подтвердить что ты не робот

Утечка памяти jQuery с удалением DOM

Здесь мертво-простая веб-страница, которая утечки памяти в IE8 с использованием jQuery (я обнаруживаю утечки памяти, наблюдая за использованием памяти моего процесса iexplore.exe со временем в диспетчере задач Windows):

<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

4b9b3361

Ответ 1

Я думал, что Дэвид может быть на что-то с предполагаемой утечкой removeChild, но я не могу воспроизвести его в IE8... это может случиться в более ранних браузерах, но это не то, что у нас есть. Если вручную удалитьChild divs нет утечки; если я изменяю jQuery, чтобы использовать outerHTML= '' (или перемещать-в-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;

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

// 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 ключ от объекта.

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

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

Ответ 2

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

Ответ 3

Удаление элементов - это неотъемлемая проблема 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 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();
});

Ответ 4

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

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

Ответ 5

По-прежнему ли он течет, если вы вызываете empty вместо remove?

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

Ответ 6

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;
                }
            }
        }
    },