【问题标题】:Should angular $watch be removed when scope destroyed?范围被破坏时是否应该删除角度 $watch?
【发布时间】:2014-09-26 15:26:56
【问题描述】:

目前正在开展一个项目,在该项目中,我们发现在未清除已破坏范围内的广播订阅时存在巨大的内存泄漏。以下代码已修复此问题:

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();
});

【问题讨论】:

  • 为什么不在当前的$scope 上使用$on?当作用域被销毁时,当前$scope 的订阅将被删除。
  • 顺便可以简单的通过 oFooEventBroadcast 注销函数如下 $scope.$on('$destroy', onFooEventBroadcast) 。你不需要自己调用它。

标签: javascript angularjs angularjs-directive angularjs-scope


【解决方案1】:

不,您不需要删除 $$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 pointing it out)

此外,请确保从超出被销毁范围的元素中取消注册任何事件侦听器。例如。如果您有指令在父节点或<body> 上注册监听器,那么您也必须取消注册它们。
同样,您不必删除在被销毁元素上注册的监听器。


有点与原始问题无关,但现在在被销毁的元素上还调度了一个 $destroyed 事件,因此您也可以挂钩(如果它适合您的用例):

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

【讨论】:

  • 另一种情况是,当您的指令具有独立作用域并且您需要在父作用域上手动查看某些内容时:$scope.$on('$destroy', $scope.$parent.$watch(..));
  • 实际上,似乎不需要取消注册事件侦听器如果它们在被销毁的范围内注册。我的意思是:$scope.$on('fooEvent', doSomething); //No need to deregister `$rootScope.$on('fooEvent', doSomething); //$rootScope 永远不会被销毁,你需要注销这个 参见:stackoverflow.com/questions/26983696/…bennadel.com/blog/…
  • @John:是的,但在 OP 的代码中(请参阅问题),他们正在$rootScope 上注册侦听器,并且必须“手动”删除。
  • @John:我刚刚看到你更新的评论。好点子。我会更新答案。谢谢!
  • @gaurav5430,是的。观看什么并不重要,重要的是谁观看(即观察者在什么范围内)。因此,如果您拨打$scope.$watch(...),则无需注销。如果您使用的是$rootScope.$watch(...),您可以使用。
【解决方案2】:

我也想添加@gkalpak 的答案,因为它引导我朝着正确的方向前进..

我正在开发的应用程序通过替换有手表的指令造成了内存泄漏。这些指令使用 jQuery 替换,然后被编译。

为了修复我添加了以下链接功能

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

它使用元素销毁事件依次销毁作用域。

【讨论】:

    猜你喜欢
    • 2016-01-04
    • 2013-06-17
    • 2018-02-26
    • 2017-11-23
    • 1970-01-01
    • 2019-06-15
    • 2012-07-09
    • 2017-08-13
    • 2015-03-21
    相关资源
    最近更新 更多