web-dev-qa-db-ja.com

angular $ watchはスコープが破壊されたときに削除されるべきですか?

現在、破壊されたスコープからブロードキャストサブスクリプションをクリアしないと、巨大なメモリリークが見つかったプロジェクトに取り組んでいます。次のコードでこれを修正しました。

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

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

このプラクティスは時計にも使用すべきですか?以下のコード例:

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

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

いいえ、$$watchersを削除する必要はありません。スコープが破棄されると効果的に削除されるためです。

Angularのソースコード(v1.2.21)から、Scope$destroyメソッド:

$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 = [];
    ...

そのため、$$watchers配列は空になります(スコープはスコープ階層から削除されます)。

配列からwatcherを削除すると、登録解除関数がすべて実行します。

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

したがって、$$watchersの登録を「手動で」解除しても意味がありません。


ただし、イベントリスナーの登録を解除する必要があります(投稿で正しく言及しているように)。

注:他のスコープに登録されているリスナーの登録を解除するだけです。破棄されるスコープに登録されているリスナーを登録解除する必要はありません。
例えば。:

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

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

それを指す )の@JohnへのThx

また、破棄されるスコープよりも長生きする要素からイベントリスナーを登録解除するようにしてください。例えば。親ノードまたは<body>にリスナーを登録するディレクティブがある場合は、それらも登録解除する必要があります。
繰り返しますが、破棄される要素に登録されているリスナーを削除する必要はありません。


元の質問とは無関係ですが、破棄される要素に$destroyedイベントもディスパッチされるので、それをフックすることもできます(ユースケースに適している場合)。

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

私も追加したいと思います @ gkalpak の答えが正しい方向に私を導くので..

私が取り組んでいたアプリケーションは、ウォッチを持っているディレクティブを置き換えることでメモリリークを作成しました。ディレクティブはjQueryを使用して置き換えられ、その後コンパイルされました。

修正するには、次のリンク機能を追加しました

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

要素のdestroyイベントを使用して、スコープを順番に破棄します。

2
Kieran