AngularJS - Entfernt $ destroy Ereignis-Listener?

200

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

Durch Abhören dieses Ereignisses können Sie Ereignis-Listener entfernen, die Speicherlecks verursachen können. In Bereichen und Elementen registrierte Listener werden automatisch bereinigt, wenn sie zerstört werden. Wenn Sie jedoch einen Listener für einen Dienst oder einen Listener für einen DOM-Knoten registriert haben, der nicht gelöscht wird, müssen Sie ihn selbst oder bereinigen Sie riskieren einen Speicherverlust.

Best Practice: Richtlinien sollten nach sich selbst bereinigen. Sie können element.on ('$ destroy', ...) oder scope. $ On ('$ destroy', ...) verwenden, um eine Bereinigungsfunktion auszuführen, wenn die Direktive entfernt wird.

Frage:

Ich habe eine element.on "click", (event) ->in meiner Direktive:

  1. Gibt es bei der Zerstörung der Direktive Speicherverweise auf die element.on, um zu verhindern, dass Müll gesammelt wird?
  2. Die Angular-Dokumentation besagt, dass ich einen Handler verwenden sollte, um Ereignis-Listener für das $destroyausgegebene Ereignis zu entfernen . Ich hatte den Eindruck, dass destroy()Ereignis-Listener entfernt wurden. Ist dies nicht der Fall?
dman
quelle

Antworten:

433

Ereignis-Listener

Zunächst ist es wichtig zu verstehen, dass es zwei Arten von "Ereignis-Listenern" gibt:

  1. Scope Event Listener registriert über $on:

    $scope.$on('anEvent', function (event, data) {
      ...
    });
    
  2. Ereignishandler, die beispielsweise über onoder an Elemente angehängt sind bind:

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

$ scope. $ destroy ()

Wenn $scope.$destroy()es ausgeführt wird, werden alle Listener entfernt, die über $ondiesen $ -Bereich registriert sind .

DOM-Elemente oder angehängte Ereignishandler der zweiten Art werden nicht entfernt.

Dies bedeutet, dass durch $scope.$destroy()manuelles Aufrufen von Beispielen innerhalb der Verknüpfungsfunktion einer Direktive weder ein über beispielsweise angehängter Handler element.onnoch das DOM-Element selbst entfernt werden.


element.remove ()

Beachten Sie, dass dies removeeine jqLite-Methode ist (oder eine jQuery-Methode, wenn jQuery vor AngularjS geladen wird) und für ein Standard-DOM-Elementobjekt nicht verfügbar ist.

Wenn element.remove()dieses Element ausgeführt wird und alle seine untergeordneten Elemente zusammen aus dem DOM entfernt werden, werden beispielsweise alle Ereignishandler über angehängt element.on.

Der dem Element zugeordnete $ scope wird nicht zerstört.

Um es verwirrender zu machen, gibt es auch ein jQuery-Ereignis namens $destroy. Wenn Sie mit jQuery-Bibliotheken von Drittanbietern arbeiten, die Elemente entfernen, oder wenn Sie sie manuell entfernen, müssen Sie in diesem Fall möglicherweise eine Bereinigung durchführen:

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

Was tun, wenn eine Richtlinie "zerstört" wird?

Dies hängt davon ab, wie die Richtlinie "zerstört" wird.

Ein normaler Fall ist, dass eine Direktive zerstört wird, weil ng-viewsich die aktuelle Ansicht ändert. In diesem Fall zerstört die ng-viewDirektive den zugehörigen $ scope, trennt alle Verweise auf den übergeordneten Bereich und ruft remove()das Element auf.

Dies bedeutet, dass wenn diese Ansicht eine Direktive mit dieser in ihrer Verknüpfungsfunktion enthält, wenn sie zerstört wird durch ng-view:

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

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

Beide Ereignis-Listener werden automatisch entfernt.

Es ist jedoch wichtig zu beachten, dass der Code in diesen Listenern immer noch Speicherlecks verursachen kann, beispielsweise wenn Sie das übliche JS-Speicherleckmuster erreicht haben circular references.

Selbst in diesem normalen Fall, in dem eine Direktive aufgrund einer Ansichtsänderung zerstört wird, müssen Sie möglicherweise einige Dinge manuell bereinigen.

Zum Beispiel, wenn Sie einen Listener registriert haben unter $rootScope:

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

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

Dies ist erforderlich, da $rootScopees während der Lebensdauer der Anwendung niemals zerstört wird.

Das Gleiche gilt, wenn Sie eine andere Pub / Sub-Implementierung verwenden, die nicht automatisch die erforderliche Bereinigung durchführt, wenn der Bereich $ zerstört wird, oder wenn Ihre Direktive Rückrufe an Dienste weiterleitet.

Eine andere Situation wäre abzubrechen $interval/ $timeout:

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

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

Wenn Ihre Direktive Ereignishandler an Elemente anfügt, z. B. außerhalb der aktuellen Ansicht, müssen Sie diese ebenfalls manuell bereinigen:

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

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

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

Dies waren einige Beispiele dafür, was zu tun ist, wenn Anweisungen von Angular "zerstört" werden, beispielsweise von ng-viewoder ng-if.

Wenn Sie benutzerdefinierte Anweisungen haben, die den Lebenszyklus von DOM-Elementen usw. verwalten, wird dies natürlich komplexer.

tasseKATT
quelle
4
'$ rootScope wird während der Lebensdauer der Anwendung niemals zerstört.' : offensichtlich, wenn Sie daran denken. Das hat mir gefehlt.
user276648
@tasseKATT Eine kleine Frage hier: Wenn wir in demselben Controller mehrere $ rootScope. $ on für verschiedene Ereignisse haben, dann rufen wir $ scope. $ on ("$ destroy", ListenerName1) auf; für jedes $ rootScope. $ auf anders ??
Yashika Garg
2
@YashikaGarg Es wäre wahrscheinlich am einfachsten, nur eine Hilfsfunktion zu haben, die alle Hörer aufruft. Wie $ scope. $ On ('$ destroy'), function () {ListenerName1 (); ListenerName2 (); ...}); Gibt es eine zusätzliche Komplexität für $ on Event-Handler in nicht isolierten Bereichen? Oder Zielfernrohre mit Zweiwegebindungen isolieren?
David Rice
Warum sollten Sie Ereignis-Listener auf $ Rootscope registrieren? Ich registriere Ereignis-Listener auf $ scope und dann führen andere Controller $ Rootscope.broadcast ('Ereignisname') aus und meine Ereignis-Listener werden ausgeführt. Werden diese Ereignis-Listener in $ scope, die Anwendungsereignisse abhören, weiterhin automatisch bereinigt?
Skychan
@Skychan Sorry, ich habe deinen Kommentar verpasst. Dies ist eine Vermutung, die jedoch möglicherweise $rootScopeaus folgenden Gründen verwendet wird : stackoverflow.com/questions/11252780/… Beachten Sie, dass dies geändert wurde , wie in der Antwort oben angegeben. Ja, Ereignis-Listener im Normalfall $scopewerden automatisch bereinigt, wenn dieser Bereich zerstört wird.
tasseKATT