AngularJS - $destroy удаляет прослушиватели событий?

https://docs.angularjs.org/guide/directive

При прослушивании этого события вы можете удалить прослушиватели событий, которые могут вызвать утечку памяти. Слушатели, зарегистрированные в области и элементы, автоматически очищаются при их уничтожении, но если вы зарегистрировали прослушиватель в службе или зарегистрировали прослушиватель на DOM node, который не удаляется, вам придется его очистить или вы рискуете ввести утечку памяти.

Лучшая практика: директивы должны очищаться после себя. Вы можете использовать element.on('$ destroy',...) или scope. $On ('$ destroy',...) для запуска функции очистки при удалении директивы.

Вопрос:

У меня есть element.on "click", (event) -> внутри моей директивы:

  • Когда директива уничтожена, есть ли ссылки на память для element.on, чтобы сохранить ее от сбора мусора? Документация
  • Angular гласит, что я должен использовать обработчик для удаления прослушивателей событий в событии $destroy. У меня создалось впечатление, что destroy() удалены прослушиватели событий, не так ли?

Ответ 1

Слушатели событий

Прежде всего важно понять, что есть два типа "прослушивателей событий":

  • Слушатели событий Scope, зарегистрированные через $on:

    $scope.$on('anEvent', function (event, data) {
      ...
    });
    
  • Обработчики событий, прикрепленные к элементам через, например, on или bind:

    element.on('click', function (event) {
      ...
    });
    

$сфера. $Уничтожить()

Когда выполняется $scope.$destroy(), он удалит всех слушателей, зарегистрированных через $on, в этой области $scope.

Он будет не удалять элементы DOM или любые присоединенные обработчики событий второго рода.

Это означает, что вызов $scope.$destroy() вручную из примера в функции директивной ссылки не удалит обработчик, прикрепленный через, например, element.on, ни сам элемент DOM.


element.remove()

Обратите внимание, что remove - это метод jqLite (или метод jQuery, если jQuery загружен до AngularjS) и недоступен для стандартного объекта элемента DOM.

Когда выполняется element.remove(), этот элемент и все его дочерние элементы будут удалены из DOM вместе, все обработчики событий будут прикреплены, например, с помощью element.on.

Он будет не уничтожать область $, связанную с элементом.

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

element.on('$destroy', function () {
  scope.$destroy();
});

Что делать, когда директива "уничтожена"

Это зависит от того, как "разрушена" директива.

В нормальном случае директива уничтожается, потому что ng-view изменяет текущий вид. Когда это произойдет, директива ng-view уничтожит связанную область $scope, разорвет все ссылки на ее родительскую область и вызовет remove() для элемента.

Это означает, что если это представление содержит директиву с этим в своей функции ссылок, когда оно уничтожено ng-view:

scope.$on('anEvent', function () {
 ...
});

element.on('click', function () {
 ...
});

Оба прослушивателя событий будут удалены автоматически.

Однако важно отметить, что код внутри этих слушателей может по-прежнему вызывать утечку памяти, например, если вы достигли общего шаблона утечки памяти JS circular references.

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

Например, если вы зарегистрировали прослушиватель на $rootScope:

var unregisterFn = $rootScope.$on('anEvent', function () {});

scope.$on('$destroy', unregisterFn);

Это необходимо, так как $rootScope никогда не уничтожается в течение всего срока службы приложения.

То же самое происходит, если вы используете другую реализацию pub/sub, которая автоматически не выполняет необходимую очистку при уничтожении области $или если ваша директива передает обратные вызовы сервисам.

Другая ситуация заключалась бы в отмене $interval/$timeout:

var promise = $interval(function () {}, 1000);

scope.$on('$destroy', function () {
  $interval.cancel(promise);
});

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

var windowClick = function () {
   ...
};

angular.element(window).on('click', windowClick);

scope.$on('$destroy', function () {
  angular.element(window).off('click', windowClick);
});

Это были некоторые примеры того, что делать, когда директивы "уничтожены" с помощью Angular, например, ng-view или ng-if.

Если у вас есть пользовательские директивы, которые управляют жизненным циклом элементов DOM и т.д., это, конечно, будет более сложным.