web-dev-qa-db-fra.com

Faut-il angular $ watch être supprimé lorsque la portée est détruite?

Travaille actuellement sur un projet où nous avons trouvé d'énormes fuites de mémoire en n'effaçant pas les abonnements de diffusion des étendues détruites. Le code suivant a résolu ce problème:

var onFooEventBroadcast = $rootScope.$on('fooEvent', doSomething);

scope.$on('$destroy', function() {
    //remove the broadcast subscription when scope is destroyed
    onFooEventBroadcast();
});

Cette pratique devrait-elle également être utilisée pour les montres? Exemple de code ci-dessous:

var onFooChanged = scope.$watch('foo', doSomething);

scope.$on('$destroy', function() {
    //stop watching when scope is destroyed
    onFooChanged();
});
53
Andrew

Non, vous n'avez pas besoin de supprimer $$watchers, car ils seront effectivement supprimés une fois la portée détruite.

À partir du code source d'Angular (v1.2.21), Scope's $destroy méthode:

$destroy: function() {
    ...
    if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
    if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
    if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
    if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
    ...
    this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = [];
    ...

Alors le $$watchers le tableau est vidé (et la portée est supprimée de la hiérarchie des portées).

La suppression de watcher du tableau est de toute façon la fonction de désinscription:

$watch: function(watchExp, listener, objectEquality) {
    ...
    return function deregisterWatch() {
        arrayRemove(array, watcher);
        lastDirtyWatch = null;
    };
}

Il est donc inutile de désenregistrer le $$watchers "manuellement".


Vous devez toujours désinscrire les auditeurs d'événements (comme vous le mentionnez correctement dans votre message)!

REMARQUE: Il vous suffit de désinscrire les écouteurs enregistrés sur d'autres étendues. Il n'est pas nécessaire de désenregistrer les écouteurs enregistrés sur la portée qui est détruite.
Par exemple.:

// You MUST unregister these
$rootScope.$on(...);
$scope.$parent.$on(...);

// You DON'T HAVE to unregister this
$scope.$on(...)

(Merci à @John pour en le signalant )

Assurez-vous également de désenregistrer les écouteurs d'événements des éléments qui survivent à la portée en cours de destruction. Par exemple. si vous avez une directive, enregistrez un écouteur sur le nœud parent ou sur <body>, alors vous devez aussi les désinscrire.
Encore une fois, vous n'avez pas à supprimer un écouteur enregistré sur l'élément en cours de destruction.


Un peu sans rapport avec la question d'origine, mais maintenant il y a aussi un $destroyed événement distribué sur l'élément en cours de destruction, vous pouvez donc vous y connecter également (si cela convient à votre cas d'utilisation):

link: function postLink(scope, elem) {
  doStuff();
  elem.on('$destroy', cleanUp);
}
85
gkalpak

Je voudrais ajouter aussi @ gkalpak la réponse car elle m'a conduit dans la bonne direction ..

L'application sur laquelle je travaillais a créé une fuite de mémoire en remplaçant les directives qui avaient des montres. Les directives ont été remplacées à l'aide de jQuery puis respectées.

Pour corriger j'ai ajouté la fonction de lien suivante

link: function (scope, elem, attrs) {
    elem.on('$destroy', function () {
        scope.$destroy();
    });
}

il utilise l'événement destroy élément pour à son tour détruire la portée.

2
Kieran