Утечки памяти в JavaScript

Я использую jquery и делаю что-то вроде этого

DOM:

<div id="parent"></div>

JS:

var _doSomeThing = function()
{
    //some codes
}

$(function()
{
    // appending div and binding methods to span
    $('#parent').append('<span>1</span>');
    $('#parent').append('<span>2</span>');
    $('#parent span').bind('click', _doSomeThing);
});

function _clearDiv()
{
    //clear div
    $('#parent').html('');
}

//sometime in future, call clear div
_clearDiv();

Теперь мой вопрос: привязывайте события к DOM, а затем просто удаляете элементы из DOM, приводящие к утечке памяти?

Если да, как решить эту проблему?

Ответ 1

Метод jQuery html пытается предотвратить утечку памяти, удалив обработчики событий для любых элементов, которые были удалены в результате вызова .html('') объекта jQuery.

Из источника 1.4.2

html: function( value ) {
    if ( value === undefined ) {
        return this[0] && this[0].nodeType === 1 ?
        this[0].innerHTML.replace(rinlinejQuery, "") :
            null;
    } 
    // See if we can take a shortcut and just use innerHTML
    // THE RELEVANT PART
    else if ( typeof value === "string" && !rnocache.test( value ) &&
        (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
        !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {

        value = value.replace(rxhtmlTag, fcloseTag);

        try {
            for ( var i = 0, l = this.length; i < l; i++ ) {
                // Remove element nodes and prevent memory leaks
               if ( this[i].nodeType === 1 ) {
                   jQuery.cleanData( this[i].getElementsByTagName("*") );
                   this[i].innerHTML = value;
                }
            }

        // If using innerHTML throws an exception, use the fallback method
        } 
        catch(e) {
            this.empty().append( value );
        }
    } 
    else if ( jQuery.isFunction( value ) ) {
        this.each(function(i){
            var self = jQuery(this), old = self.html();
            self.empty().append(function(){
                return value.call( this, i, old );
            });
        });

    }
    else {
        this.empty().append( value );
    }
    return this;
}

Мы видим, что вызывается функция jQuery.cleanData(). Вот источник этого

cleanData: function( elems ) {
    var data, id, cache = jQuery.cache,
        special = jQuery.event.special,
        deleteExpando = jQuery.support.deleteExpando;

    for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
        id = elem[ jQuery.expando ];

        if ( id ) {
            data = cache[ id ];

            if ( data.events ) {
                for ( var type in data.events ) {
                    if ( special[ type ] ) {
                        jQuery.event.remove( elem, type );

                    } else {
                        removeEvent( elem, type, data.handle );
                    }
                }
            }

            if ( deleteExpando ) {
                delete elem[ jQuery.expando ];

            } else if ( elem.removeAttribute ) {
                elem.removeAttribute( jQuery.expando );
            }

            delete cache[ id ];
        }
    }
}

Это выглядит в объекте jQuery.cache для любых свойств типа события в свойстве объекта событий объекта данных, относящегося к каждому элементу, который будет удален при вызове .html('') и удаляет их.

Чтобы объяснить, как работает стандартное связывание событий, когда функция привязана как обработчик события, поднятого на элементе с помощью jQuery, объект данных добавляется как свойство объекта jQuery.cache. Этот объект данных содержит объект свойств событий, который будет иметь свойство, созданное на нем, с именем, соответствующим типу события, к которому вы хотите привязать функцию обработчика события. это свойство будет содержать массив функций, который должен вызываться, когда событие возникает на элементе, поэтому функция обработчика события добавляется в этот массив. Если это первая функция обработчика события для типа события и соответствующего элемента, функция jQuery.event.handle с вызовом применить (используя этот элемент в качестве контекста, так что this в контексте выполнения функции будет ссылаться на элемент) регистрируется в браузере с помощью addEventListener/attachEvent.

Когда событие возбуждено, функция jQuery.event.handle вызывает все функции в массиве в свойстве объекта свойств событий объекта данных, соответствующего типу события и элементу, на котором было создано событие.

Таким образом, html('') не должно вызывать утечки памяти, поскольку существует ряд защитных мер, чтобы предотвратить их.

Ответ 2

Невозможно прокомментировать проблему утечки, но вы можете просто использовать .empty() вместо .html(''). Таким образом вы очистите innerHTML и удалите все обработчики связанных событий.

Ответ 3

Да, поскольку jQuery поддерживает список подключенных обработчиков событий, чтобы упростить их отключение и для того, чтобы явно отцепить их для вас, когда страница выгружается (что работает с более серьезной утечкой памяти в IE). (Так что Prototype не может говорить для других libs.) Решение состоит в том, чтобы отцепить их перед удалением элементов (либо напрямую, либо через empty).

Ответ 4

Вы всегда можете использовать $('#parent span').unbind();, чтобы быть уверенным

Ответ 5

Поскольку вы постоянно ссылаетесь на $('# parent'), вы должны создать ссылку на этот объект в глобальной области, чтобы jQuery не постоянно искал объект по каждому запросу. Выполняя это, вы по существу кэшируете ссылку на объект, который значительно сократит использование памяти.

_parent = $('#parent');

...

function(){ _parent.append('<span>1</span>'); }

Изменить: я взял этот совет из этой статьи в jQuery Performance Rules