AngularJS - Czy $ destroy usuwa detektory zdarzeń?

200

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

Słuchając tego zdarzenia, możesz usunąć detektory zdarzeń, które mogą powodować wycieki pamięci. Detektory zarejestrowane w zakresach i elementach są automatycznie czyszczone po ich zniszczeniu, ale jeśli zarejestrowałeś detektor w usłudze lub zarejestrował detektor w węźle DOM, który nie jest usuwany, będziesz musiał go wyczyścić samodzielnie lub ryzykujesz wyciekiem pamięci.

Najlepsza praktyka: dyrektywy powinny po sobie posprzątać. Możesz użyć element.on ('$ destroy', ...) lub scope. $ On ('$ destroy', ...), aby uruchomić funkcję czyszczenia po usunięciu dyrektywy.

Pytanie:

Mam w element.on "click", (event) ->środku moją dyrektywę:

  1. Czy po zniszczeniu dyrektywy istnieją odniesienia do pamięci, element.onktóre zapobiegają gromadzeniu śmieci?
  2. Dokumentacja kątowa mówi, że powinienem użyć modułu obsługi, aby usunąć detektory zdarzeń z $destroyemitowanego zdarzenia. Miałem wrażenie, że destroy()usunięto detektory zdarzeń, czy tak nie jest?
dman
źródło

Odpowiedzi:

433

Detektory zdarzeń

Po pierwsze, ważne jest, aby zrozumieć, że istnieją dwa rodzaje „detektorów zdarzeń”:

  1. Zakres detektorów zdarzeń zarejestrowanych za pośrednictwem $on:

    $scope.$on('anEvent', function (event, data) {
      ...
    });
  2. Procedury obsługi zdarzeń dołączone do elementów na przykład onlub bind:

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

$ scope. $ destroy ()

Po $scope.$destroy()wykonaniu usunie wszystkich słuchaczy zarejestrowanych za pośrednictwem $ontego zakresu $.

To będzie nie usuwać elementów DOM i wszystkie podłączone obsługi zdarzeń drugiego rodzaju.

Oznacza to, że $scope.$destroy()ręczne wywołanie z przykładu w ramach funkcji link dyrektywy nie usunie element.onmodułu obsługi dołączonego na przykład ani samego elementu DOM.


element.remove ()

Zauważ, że removejest to metoda jqLite (lub metoda jQuery, jeśli jQuery jest ładowane przed AngularjS) i nie jest dostępna w standardowym obiekcie elementu DOM.

Kiedy element.remove()zostanie wykonany, ten element i wszystkie jego elementy potomne zostaną razem usunięte z DOM, a wszystkie procedury obsługi zdarzeń zostaną podłączone na przykład za pośrednictwem element.on.

To będzie nie zniszczyć zakresu $ powiązany z elementem.

Aby było bardziej mylące, istnieje również zdarzenie jQuery $destroy. Czasami podczas pracy z bibliotekami jQuery innych firm, które usuwają elementy, lub jeśli usuwasz je ręcznie, może być konieczne wykonanie czyszczenia, gdy tak się stanie:

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

Co zrobić, gdy dyrektywa zostaje „zniszczona”

Zależy to od „zniszczenia” dyrektywy.

Normalnym przypadkiem jest zniszczenie dyrektywy, ponieważ ng-viewzmienia ona obecny widok. Kiedy tak się stanie, ng-viewdyrektywa zniszczy powiązany zakres $, zerwie wszystkie odniesienia do zakresu nadrzędnego i wywołanie remove()elementu.

Oznacza to, że jeśli ten widok zawiera dyrektywę z tą funkcją w funkcji link, gdy zostanie zniszczony przez ng-view :

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

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

Oba detektory zdarzeń zostaną automatycznie usunięte.

Należy jednak pamiętać, że kod wewnątrz tych detektorów może nadal powodować wycieki pamięci, na przykład jeśli osiągnąłeś wspólny wzór wycieku pamięci JS circular references .

Nawet w tym normalnym przypadku zniszczenia dyrektywy z powodu zmiany widoku istnieją rzeczy, które mogą być potrzebne do ręcznego wyczyszczenia.

Na przykład, jeśli zarejestrowałeś nasłuchującego na $rootScope:

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

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

Jest to potrzebne od tego czasu $rootScope nigdy nie jest niszczone w trakcie życia aplikacji.

To samo dotyczy sytuacji, gdy używasz innej implementacji pub / sub, która nie wykonuje automatycznie koniecznego czyszczenia po zniszczeniu zakresu $ lub jeśli twoja dyrektywa przekazuje wywołania zwrotne do usług.

Inną sytuacją byłoby anulowanie $interval/ $timeout:

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

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

Jeśli twoja dyrektywa dołącza procedury obsługi zdarzeń do elementów, na przykład poza bieżącym widokiem, musisz je również ręcznie wyczyścić:

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

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

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

Oto kilka przykładów tego, co zrobić, gdy dyrektywy zostaną „zniszczone” przez Angulara, na przykład przez ng-viewlub ng-if.

Jeśli masz niestandardowe dyrektywy, które zarządzają cyklem życia elementów DOM itp., Będzie to oczywiście bardziej skomplikowane.

tasseKATT
źródło
4
„$ rootScope nigdy nie jest niszczony podczas życia aplikacji”. : oczywiste, kiedy o tym pomyślisz. Tego mi brakowało.
user276648,
@tasseKATT Małe pytanie tutaj, jeśli w tym samym kontrolerze mamy wiele $ rootScope. $ on dla różnych zdarzeń, wówczas powinniśmy wywołać $ scope. $ on ("$ destroy", ListenerName1); dla każdego $ rootScope. $ na inaczej?
Yashika Garg
2
@YashikaGarg Prawdopodobnie najłatwiej byłoby po prostu mieć funkcję pomocnika, która wywołuje wszystkich słuchaczy. Podobnie jak $ scope. $ On ('$ destroy'), function () {ListenerName1 (); ListenerName2 (); ...}); Czy jest jakaś dodatkowa złożoność $ dla procedur obsługi zdarzeń w zakresach nieizolowanych? Lub izolować lunety za pomocą wiązań dwukierunkowych?
David Rice
Po co rejestrować detektory zdarzeń na $ rootscope? Rejestruję detektory zdarzeń w $ scope, a następnie inne kontrolery wykonują $ rootscope.broadcast ('nazwa zdarzenia') i moje detektory zdarzeń działają. Czy te detektory zdarzeń w zakresie $, które nasłuchują zdarzeń aplikacji, nadal będą automatycznie czyszczone?
Skychan
@Skychan Przepraszam, że przegapiłem twój komentarz. To przypuszczenie, ale ludzie mogą $rootScopez tego powodu skorzystać : stackoverflow.com/questions/11252780/... Pamiętaj, że ponieważ odpowiedź u góry jest zmieniona. Tak, nasłuchiwanie zdarzeń normalnie $scopezostanie automatycznie wyczyszczone, gdy ten zakres zostanie zniszczony.
tasseKATT