web-dev-qa-db-ja.com

AngularJS - $ destroyはイベントリスナを削除しますか?

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

このイベントをリッスンすることで、メモリリークを引き起こす可能性があるイベントリスナーを削除できます。スコープおよび要素に登録されたリスナーは破棄されると自動的にクリーンアップされますが、サービスにリスナーを登録した場合、または削除されていないDOMノードにリスナーを登録した場合は自分でクリーンアップする必要があります。あなたはメモリリークを引き起こす危険があります。

ベストプラクティス:指令は自分自身の後で片付けるべきです。ディレクティブが削除されたときにクリーンアップ関数を実行するには、element.on( '$ destroy'、...)またはscope。$ on( '$ destroy'、...)を使用できます。

質問:

ディレクティブの中にelement.on "click", (event) ->があります。

  1. ディレクティブが破壊されたとき、ガベージコレクションされないようにするためにelement.onへのメモリ参照はありますか?
  2. Angularのドキュメントでは、$destroyが発行したイベントのイベントリスナーを削除するにはハンドラを使うべきだと述べています。 destroy()がイベントリスナーを削除したという印象を受けましたが、そうではありませんか?
194
dman

イベントリスナー

まず、2種類の「イベントリスナー」があることを理解することが重要です。

  1. $onで登録されたスコープイベントリスナー:

    $scope.$on('anEvent', function (event, data) {
      ...
    });
    
  2. onbindなどを介して要素に結びつけられたイベントハンドラ:

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

$スコープ。$ destroy()

$scope.$destroy()が実行されると、その$スコープに$onを通して登録されているすべてのリスナーを削除します。

notDOM要素や第2種の添付イベントハンドラを削除します。

つまり、ディレクティブのリンク関数内でexampleから$scope.$destroy()を手動で呼び出しても、たとえばelement.onを介してアタッチされたハンドラもDOM要素自体も削除されません。


element.remove()

removeはjqLit​​eメソッド(またはjQueryがAngularjSの前にロードされている場合はjQueryメソッド)であり、標準のDOM要素オブジェクトでは使用できないことに注意してください。

element.remove()が実行されると、その要素とそのすべての子が一緒にDOMから削除され、すべてのイベントハンドラが例えばelement.onを通じてアタッチされます。

それはnot要素に関連する$スコープを破壊するでしょう。

さらに分かりやすくするために、$destroyというjQueryイベントもあります。要素を削除するサードパーティのjQueryライブラリを使用している場合、または手動で要素を削除した場合は、その場合にクリーンアップを実行する必要があります。

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

指令が「破壊された」ときの対処方法

これは指令がどのように「破壊される」かによって異なります。

ng-viewは現在のビューを変更するため、通常はディレクティブが破壊されます。これが起こると、ng-viewディレクティブは関連する$スコープを破壊し、その親スコープへのすべての参照を切断し、その要素に対してremove()を呼び出します。

これは、そのビューがng-viewによって破棄されたときにそのリンク関数にthisを含むディレクティブが含まれている場合です。

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

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

両方のイベントリスナーは自動的に削除されます。

ただし、たとえば、一般的なJSメモリリークパターンcircular referencesを達成した場合など、これらのリスナー内のコードによってメモリリークが発生する可能性があることに注意することが重要です。

ビューが変更されたためにディレクティブが破壊されるこの通常の場合でも、手動でクリーンアップする必要があるかもしれません。

例えば$rootScopeにリスナーを登録したならば:

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

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

$rootScopeはアプリケーションの有効期間中に破棄されることはないため、これが必要です。

$ scopeが破棄されたときに必要なクリーンアップを自動的に実行しない別の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);
});

ng-viewng-ifなど、Angularによってディレクティブが「破棄」されたときの対処方法の例をいくつか示しました。

DOM要素などのライフサイクルを管理するカスタムディレクティブがある場合は、もちろんもっと複雑になります。

427
tasseKATT