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

Posted

技术标签:

【中文标题】AngularJS - $destroy 是不是删除事件监听器?【英文标题】:AngularJS - Does $destroy remove event listeners?AngularJS - $destroy 是否删除事件监听器? 【发布时间】:2015-01-15 00:20:57 【问题描述】:

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

通过监听此事件,您可以移除可能导致内存泄漏的事件监听器。注册到作用域和元素的监听器在销毁时会自动清理,但如果您在服务上注册了监听器,或者在未被删除的 DOM 节点上注册了监听器,则必须自己清理或您可能会导致内存泄漏。

最佳实践:指令应自行清理。您可以使用 element.on('$destroy', ...) 或 scope.$on('$destroy', ...) 在指令被删除时运行清理功能。

问题:

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

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

【问题讨论】:

【参考方案1】:

事件监听器

首先,重要的是要了解有两种“事件侦听器”:

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

    $scope.$on('anEvent', function (event, data) 
      ...
    );
    

    通过例如onbind 附加到元素的事件处理程序:

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

$scope.$destroy()

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

不会删除 DOM 元素或任何附加的第二类事件处理程序。

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


element.remove()

请注意,remove 是一个 jqLit​​e 方法(如果 jQuery 在 AngularjS 之前加载,则为 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 销毁时在其链接函数中包含带有 this 的指令:

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 元素等生命周期的自定义指令,它当然会变得更加复杂。

【讨论】:

'$rootScope 在应用程序的生命周期内永远不会被销毁。' : 一想到就明白了。这就是我所缺少的。 @tasseKATT 这里有个小问题,如果在同一个控制器中我们有多个 $rootScope.$on 用于不同的事件,那么我们应该调用 $scope.$on("$destroy", ListenerName1);对于每个 $rootScope.$on 不同?? @YashikaGarg 拥有一个调用所有侦听器的辅助函数可能是最简单的。像 $scope.$on('$destroy'), function() ListenerName1(); ListenerName2();...);非隔离范围上的 $on 事件处理程序是否有任何额外的复杂性?或者使用两种方式绑定来隔离作用域? 为什么要在 $rootscope 上注册事件监听器?我在 $scope 上注册事件侦听器,然后其他控制器执行 $rootscope.broadcast('eventname') 并且我的事件侦听器运行。 $scope 上正在监听应用程序事件的这些事件监听器是否仍将被自动清理? @Skychan 抱歉,我错过了您的评论。这是一个猜测,但人们可能会因此而使用$rootScope:***.com/questions/11252780/… 请注意,正如顶部的答案所述,这已被更改。是的,普通$scope 上的事件侦听器将在该范围被销毁时自动清理。

以上是关于AngularJS - $destroy 是不是删除事件监听器?的主要内容,如果未能解决你的问题,请参考以下文章

AngularJs-destroy事件 (页面离开事件)

angularjs中的$destroy和$timeout

如何修改依赖::destroy 查询 has_many 关系

Codeforces543 B. Destroying Roads

测试后 SpringJUnit4ClassRunner 是不是仍然不能可靠地调用 DisposableBean.destroy

在函数内部创建图表时,销毁 chart.js 不起作用 - chart.destroy() 不是函数