AngularJS - $ destroy是否删除事件监听器?

时间:2014-11-17 23:09:48

标签: angularjs memory-leaks

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

  

通过侦听此事件,您可以删除可能导致内存泄漏的事件侦听器。注册到范围和元素的监听器在销毁时会自动清理,但如果您在服务上注册了监听器,或者在未删除的DOM节点上注册了监听器,则必须自行清理或者你冒着引入内存泄漏的风险。

     

最佳实践:指令应该自行清理。您可以使用element.on('$ destroy',...)或范围。$ on('$ destroy',...)来删除指令时运行清理函数。

问题:

我的指令中有element.on "click", (event) ->

  1. 当指令被销毁时,是否有任何对element.on的内存引用以防止它被垃圾收集?
  2. Angular文档声明我应该使用处理程序删除$destroy发出的事件上的事件侦听器。我的印象是destroy()删除了事件监听器,情况不是这样吗?

1 个答案:

答案 0 :(得分:426)

事件监听器

首先,了解有两种“事件监听器”非常重要:

  1. 通过$on注册的范围事件监听器:

    $scope.$on('anEvent', function (event, data) {
      ...
    });
    
  2. 通过例如onbind附加到元素的事件处理程序:

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

  3. $范围。$破坏()

    执行$scope.$destroy()时,它将删除在该$ scope上通过$on注册的所有侦听器。

    它将删除第二种DOM元素或任何附加的事件处理程序。

    这意味着从指令的link函数中的示例手动调用$scope.$destroy()将不会删除通过例如element.on附加的处理程序,也不会删除DOM元素本身。


    element.remove()

    请注意remove是一个jqLit​​e方法(如果在AngularjS之前加载jQuery,则为jQuery方法),并且在标准DOM元素对象上不可用。

    执行element.remove()时,该元素及其所有子元素将从DOM中删除,所有事件处理程序将通过例如element.on附加。

    会破坏与该元素关联的$ scope。

    为了让它更加混乱,还有一个名为$destroy的jQuery事件。有时在使用删除元素的第三方jQuery库时,或者如果手动删除它们,您可能需要在发生这种情况时执行清理:

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

    指令被“销毁”时该怎么办

    这取决于指令是如何被“销毁”的。

    正常情况是指令被销毁,因为ng-view更改了当前视图。发生这种情况时,ng-view指令将销毁关联的$ scope,切断对其父作用域的所有引用,并在元素上调用remove()

    这意味着如果该视图在其被ng-view销毁时在其链接函数中包含一个指令:

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

    两个事件侦听器都将自动删除。

    但是,重要的是要注意这些侦听器中的代码仍然可能导致内存泄漏,例如,如果您已经实现了常见的JS内存泄漏模式 circular references

    即使在正常情况下指令因视图更改而被销毁,也可能需要手动清理。

    例如,如果您在$rootScope上注册了一个监听器:

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

    这是必需的,因为$rootScope在应用程序的生命周期内永远不会被销毁。

    如果您使用的另一个pub / sub实现在$ scope被销毁时没有自动执行必要的清理,或者您的指令将回调传递给服务,那么同样如此。

    另一种情况是取消$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);
    });
    

    以下是Angular“销毁”指令时应采取的措施的一些示例,例如ng-viewng-if

    如果您有自定义指令来管理DOM元素的生命周期等,它当然会变得更复杂。