在新作用域下使用angular's $compile时发生内存泄漏

Memory leak when using angular's $compile with a new scope

本文关键字:compile 泄漏 内存 作用域 angular      更新时间:2023-09-26

我想使用javascript动态创建angular组件,然后让angular使用$compile与新创建的作用域编译它们。然后,当我不再使用该组件时,我想要销毁该组件和新的作用域。

一切都像预期的那样工作,除了一个事实,即使我销毁了新的作用域,它所使用的所有内存都不会被释放。

下面是该代码的部分简化版本:

app.controller("mainCtrl", ["$scope", "$compile", function($scope, $compile) {
    var childScope;
    //call this every time the button is clicked
    this.createDirective = function() {
        //dynamically create a new instance of the custom directive
        var customDirective = document.createElement("custom-directive");
        //if another child scope exists, destroy it
        if (childScope) {
            childScope.$destroy();
            childScope = undefined;
        }
        //create a new child scope
        childScope = $scope.$new();
        //compile the custom directive
        $compile(customDirective)(childScope);
    };
}]);

此代码的完整工作示例在这里

所有这些代码所做的,是每次点击按钮时创建一个新组件,但首先销毁任何已经存在的组件。请注意,我实际上并没有在页面中添加已编译的组件,因为我注意到无论我是否使用它,泄漏仍然存在。

使用Chrome的开发工具(配置文件->记录分配时间线->开始),我看到点击按钮后,以下内存使用情况几次:

内存消耗

很明显,即使调用了作用域的$destroy函数,customDirective所占用的任何内存也不会真正释放。

我过去成功地使用了$compile而没有创建新的作用域,但似乎我在这种情况下缺少了一些东西。我是否还应该做一些其他的事情来确保没有对新作用域的引用?

<标题>编辑

根据下面JoelCDoyle的回答,这里是修复(我在我创建的作用域中添加了一个on destroy函数):

app.controller("mainCtrl", ["$scope", "$compile", function($scope, $compile) {
    var childScope;
    //call this every time the button is clicked
    this.createDirective = function() {
        //dynamically create a new instance of the custom directive
        var customDirective = document.createElement("custom-directive");
        //if another child scope exists, destroy it
        if (childScope) {
            childScope.$destroy();
            childScope = undefined;
        }
        //create a new child scope
        childScope = $scope.$new();
        //compile the custom directive
        var compiledElement = $compile(customDirective)(childScope);
        //FIX: remove the angular element
        childScope.$on("$destroy", function() {
            compiledElement.remove();
        });
    };
}]);
固定小提琴

我想我已经找到了一个解决方案:https://jsfiddle.net/yqw1dk0w/8/

app.directive('customDirective', function(){
  return {
    template: '<div ng-controller="customDirectiveCtrl"></div>',
    link: function(scope, element) {
      scope.$on('$destroy', function() {
        element.remove();
      });
    }
  };
});

我仍然不太清楚为什么会这样,但是angular编译文档中的如何编译指令这一节提供了一个线索:https://docs.angularjs.org/guide/compiler

$compile通过调用组合函数将模板与作用域链接起来上一步中的链接函数。这反过来又会调用单个指令的链接函数,注册监听器在元素上设置$watch,并将作用域设置为each指令配置为do.'

这样做的结果是作用域和DOM之间的动态绑定。因此,在这一点上,在编译范围内的模型中的更改将反映在DOM中。

销毁作用域,我猜,不会删除这些元素侦听器。这就是上面代码所做的:destroy指令/子作用域上的作用域destroy

如果将数组放入作用域并取消分配,它将开始释放

$scope.array.length = 0;

到析构函数。但是…很高兴知道。我必须密切关注内存消耗。似乎保留了范围。因为我只是取消分配内部变量