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

Обработчики событий на DOM node удаляются с помощью node?

(Примечание: я использую jQuery ниже, но вопрос действительно является общим Javascript.)

Скажем, у меня есть div#formsection, содержимое которого повторно обновляется с использованием AJAX, например:

var formSection = $('div#formsection');
var newContents = $.get(/* URL for next section */);
formSection.html(newContents);

Всякий раз, когда я обновляю этот div, я запускает настраиваемое событие, которое связывает обработчики событий с некоторыми из вновь добавленных элементов, например:

// When the first section of the form is loaded, this runs...
formSection.find('select#phonenumber').change(function(){/* stuff */});

...

// ... when the second section of the form is loaded, this runs...
formSection.find('input#foo').focus(function(){/* stuff */});

Итак: я привязываю обработчики событий к некоторым узлам DOM, а затем удаляет эти узлы DOM и вставляет новые (html() делает это) и связывает обработчики событий с новыми узлами DOM.

Удалены ли мои обработчики событий вместе с узлами DOM, к которым они привязаны? Другими словами, при загрузке новых разделов много бесполезных обработчиков событий, накапливаемых в памяти браузера, ожидающих для событий на узлах DOM, которые больше не существуют или они очищаются, когда удалены узлы DOM?

Бонусный вопрос: как можно проверить это самостоятельно?

4b9b3361

Ответ 1

Функции обработчика событий подчиняются той же Мусорной коллекции, что и другие переменные. Это означает, что они будут удалены из памяти, когда интерпретатор определит, что нет возможности получить ссылку на эту функцию. Простое удаление node, однако, не гарантирует сбор мусора. Например, возьмите этот node и связанный с ним обработчик событий

var node = document.getElementById('test');
node.onclick = function() { alert('hai') };

Теперь удалим node из DOM

node.parentNode.removeChild(node);

Итак, node больше не будет отображаться на вашем веб-сайте, но он, очевидно, все еще существует в памяти, как и обработчик событий

node.onclick(); //alerts hai

Пока ссылка на node все равно доступна, связанные свойства (из которых onclick равно единице) остаются неизменными.

Теперь попробуйте, не создавая оборванную переменную

document.getElementById('test').onclick = function() { alert('hai'); }

document.getElementById('test').parentNode.removeChild(document.getElementById('test'));

В этом случае, похоже, нет никакого другого способа получить доступ к DOM node #test, поэтому, когда цикл сбора мусора запускается, обработчик onclick должен быть удален из памяти.

Но это очень простой случай. Использование Javascript закрытий может значительно усложнить определение собираемости мусора. Давайте попробуем привязать несколько более сложную функцию обработчика событий к onclick

document.getElementById('test').onclick = function() {
  var i = 0;
  setInterval(function() {
    console.log(i++);
  }, 1000);

  this.parentNode.removeChild(this);
};

Итак, когда вы нажмете на #test, элемент будет немедленно удален, но через секунду, а затем каждую секунду вы увидите, что на вашем консоли будет увеличен номер. node удаляется, и никакая дальнейшая ссылка на него невозможна, но, кажется, ее части остаются. В этом случае сама функция обработчика событий, скорее всего, не сохраняется в памяти, а область ее создания.

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

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

Ответ 2

jQuery подходит для устранения утечек памяти при удалении элементов из DOM. Пока вы используете jQuery для удаления узлов DOM, удаление обработчиков событий и дополнительных данных должно обрабатываться jQuery. Я бы очень рекомендовал прочитать статью "Сценарий JavaScript Ninja" Джона Резига поскольку он подробно описывает потенциальные утечки в разных браузерах и как библиотеки JavaScript, такие как jQuery get вокруг этих вопросов. Если вы не используете jQuery, вам обязательно нужно беспокоиться о утечке памяти через осиротевших обработчиков событий при удалении узлов DOM.

Ответ 3

Вам может потребоваться удалить эти обработчики событий.

Утечки памяти Javascript после разгрузки веб-страницы

В нашем коде, который не основан на jQuery, но какой-то прототип deviant, у нас есть инициализаторы и деструкторы в наших классах. Нам было крайне необходимо удалить обработчики событий из объектов DOM, когда мы уничтожаем не только наше приложение, но и отдельные виджеты во время выполнения.

В противном случае мы закончим утечку памяти в IE.

Удивительно легко получить утечки памяти в IE - даже когда мы выгружаем страницу, мы должны быть уверены, что приложение "выключается" чисто, убирает все - или процесс IE будет расти со временем.

Изменить: Чтобы сделать это правильно, у нас есть наблюдатель событий на window для события unload. Когда это событие наступает, наша цепочка деструкторов призвана правильно очищать каждый объект.

И некоторый пример кода:

/**
 * @constructs
 */
initialize: function () {
    // call superclass
    MyCompany.Control.prototype.initialize.apply(this, arguments);

    this.register(MyCompany.Events.ID_CHANGED, this.onIdChanged);
    this.register(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);
},

destroy: function () {

    if (this.overMap) {
        this.overMap.destroy();
    }

    this.unregister(MyCompany.Events.ID_CHANGED, this.onIdChanged);
    this.unregister(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);

    // call superclass
    MyCompany.Control.prototype.destroy.apply(this, arguments);
},

Ответ 4

Не обязательно

Документация по методу jQuery empty() отвечает на мой вопрос и дает мне решение моей проблемы. В нем говорится:

Чтобы избежать утечек памяти, jQuery удаляет другие конструкции, такие как данные и обработчики событий из дочерних элементов перед удалением элементов сами по себе.

Итак: 1) если бы мы не делали этого явно, у нас были бы утечки памяти, а 2) с помощью empty(), я могу избежать этого.

Поэтому я должен это сделать:

formSection.empty();
formSection.html(newContents);

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

Ответ 5

Я хотел узнать себя так после небольшого теста, я думаю, что ответ да.

removeEvent вызывается, когда вы .remove() что-то из DOM.

Если вы хотите увидеть его сами, вы можете попробовать это и выполнить код, установив точку останова. (Я использовал jquery 1.8.1)

Добавить новый div первым:
  $('body').append('<div id="test"></div>')

Проверьте $.cache, чтобы убедиться, что к нему не подключены события. (это должен быть последний объект)

Присоедините к нему событие click:
  $('#test').on('click',function(e) {console.log("clicked")});

Проверьте его и посмотрите новый объект в $.cache:
  $('#test').click()

Удалите его, и вы можете увидеть, что объект в $.cache также исчез:
  $('#test').remove()