【问题标题】:Mysterious memory leaks in Angular JS Single Page ApplicationAngular JS 单页应用程序中的神秘内存泄漏
【发布时间】:2015-01-28 19:54:14
【问题描述】:

我非常绝望地来找你 - 已经为此工作了两天。

我们有一个使用 Angular JS 构建的单页应用程序。我们使用 HTML5 模式下的 $routeProvider 来实现 SPA 路由。功能方面 - 一切都很好!

我们在 body 元素上附加了一个全局控制器,标题中有一个用于快速搜索功能的控制器,所有其他控制器的范围都限定为路由。控制器之间共享一些数据,例如 currentUser 对象和 ViewRes 对象,其中包含用户所选语言的字符串值。

但是,我们注意到 Chrome 服务为我们的页面占用了太多 RAM。我使用 Chrome Profiles 工具查看发生了什么。我禁用了大部分使用复杂指令的代码,只省略了基础知识。内存消耗降低了很多,但仍然明显存在。每当我更改页面时,内存都会增加。

在堆快照中,它显示大部分内存被(闭包)和(数组)占用。 Detached DOM 树也很大。请注意,这些快照包含我们应用程序的裸元素(页眉、页脚和轻量级内容)。如果我包括我们复杂的 UI 组件,那么内存会从 14MB 跳跃到 50MB 到 140MB ……甚至更多。显然我们会注意这些指令,但我担心我们的问题是全球性的,而不仅仅是指令设计不当。

当我打开(数组)元素时,我注意到其中有一堆大小为 6172 的浅大小和保留大小。跟踪该对象的范围总是会导致一些 ng 指令,如 ngShow、ngIf...

从图片中可以看出,树在“函数()中的缓存”处结束。我们使用 Angular 1.3.6。


编辑:这个项目还包括 jQuery。我们使用的是 jQuery 1.8.2,当我切换到 1.11.2 时,在简单页面(简单 ng-repeats 和简单模型)之间切换不再导致内存泄漏(不再分离 DOM 元素)。

现在复杂的指令仍然给我太多的分离元素,所以我现在要处理这些,当我找出原因时我会在这里发布结果。


【问题讨论】:

  • 你能在 plunker 中复制这个吗?
  • @NewDev 这真的很难做到。请参阅我编辑的问题。谢谢!

标签: angularjs performance memory-leaks


【解决方案1】:

很难说出您的具体问题是什么,但 Angular 中内存泄漏的常见位置包括 $interval、$watches 和事件处理程序。这些函数中的每一个都会创建一个不会被清理的闭包,除非您在控制器拆除时明确删除它。

$interval 尤其令人讨厌,因为它会继续运行,直到您关闭浏览器或网页 - 即使用户移动到不同的选项卡或应用程序,它也不会停止运行!

如果您在这些闭包中创建对 DOM 元素的引用,您很快就会开始咀嚼内存,因为这些引用永远不会被释放,并且 DOM 树会随着用户从一个页面移动到另一个页面而分离。

要解决此问题,请确保在控制器(以及指令的控制器或链接函数)中处理 $destroy 事件,并在使用任何间隔、监视或事件处理程序后显式清理。

您可以通过持有对每个 $interval、watch 或事件处理程序的引用并简单地将其作为 $destroy 事件处理程序中的函数调用来做到这一点。

例如:

// eventListener to remove
var eventListener = $scope.$on('eventName', function(){…});

// remove the eventListener when the $destroy event is fired
$scope.$on('$destroy', function(){
    // call the value returned from $scope.$on as a function to remove
    // the event listener
    eventListener();
}

// remove an event listener defined on a DOM node:
var elementEventListener = element.on('eventName', function(){…});

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

// Stop an interval
var stop = $interval(function(){...});
$scope.$on('$destroy', function(){
    stop();
}


// Finally, unbind a  $watch
var watchFn = $scope.$watch('someValue', function(newVal){…}

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

最后,永远不要在作用域中存储 DOM 元素! (原因见第 2 点here)。

【讨论】:

  • 感谢您的回复。我持有对 DOM 的引用并在我的指令中使用一些事件和 $timeout 的原因是因为我需要在元素被解析到浏览器后获取元素的宽度,然后决定是否显示其他元素(实际上是滚动箭头)。宽度是动态的,因为它可以在不同的屏幕上缩放。您对此有什么建议吗?
  • 在指令中保存对 DOM 的引用并没有错——只是不要将范围变量分配给 DOM 元素。并且始终确保您按照我上面所说的那样处理 $destroy 事件,并清理您的 $watches、事件处理程序和 $intervals。
猜你喜欢
  • 1970-01-01
  • 2013-07-25
  • 1970-01-01
  • 2011-02-24
  • 2013-06-26
  • 2018-12-10
  • 2014-12-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多