内存泄漏:高速缓存中的剩余元素和AngularJs中的data_user

时间:2014-07-29 13:00:37

标签: javascript jquery angularjs svg

我使用ng-repeat创建元素(有些是SVG标签,有些是简单的HTML)。在数据模型的变化 - 在新数据到达时重置的对象 - 总是留下作为分离的DOM元素的元素。它们是这样的:

Screenshot from Chrome Developer Tools

元素是data_user的一部分,它似乎是jquery的一部分。在数据更改的几个地方会出现此问题。似乎观察者是问题,因为他们一直在参考他们的表达。

创建元素,例如像这样:

.directive('svgGraphic', ['$compile', function ($compile) {
    return {
        restrict: 'E',
        replace: false,
        link: function (scope, element, attrs) {
            var svgData = scope.model.getAttribute("svgGraphic");
            var svgDomElement =  $(svgData.svg);
            scope.layers = svgData.layers;

            svgDomElement.append('<svg-layer ng-repeat="layer in layers"></svg-layer>');
            element.append($compile(svgDomElement)(scope));

            scope.$on("$destroy", function() {
                scope.$$watchers = null;
                scope.$$listeners = null;
            })
        }
    };
}])

解决方法是手动删除观察者和听众,如上所示 - 我认为什么是不好的解决方案!

当来自服务器的新数据到达时,它设置如下:

$scope.model = model;
$scope.$digest();

仅替换模型数据是否有问题?

有什么想法角度如何不会删除旧元素上的听众?当ng-repeat接收新数据并重建所有元素时,Angular应删除所有观察者。

2 个答案:

答案 0 :(得分:1)

我发现了同样的问题。我创建了一个Watcher类,然后使用分析器,我可以计算Watcher实例。当我浏览应用程序时,我看到实例继续增加,一些实例由data_user缓存保留:(。

我还修复了删除childScopes观察者的问题,并在范围内添加了一些元数据,如childScope列表。

这是我更改的角度代码(仅我更改的功能)。我希望这可以帮助你找到错误,我仍在与之抗争:)

function $RootScopeProvider() {


 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
  function($injector, $exceptionHandler, $parse, $browser) {

 var watcherCount = 0;

function Watcher(listener, initWatchVal, get, watchExp, objectEquality, scope) {
  this.fn = isFunction(listener) ? listener : noop;
  this.last = initWatchVal;
  this.get = get;
  this.exp = watchExp;
  this.eq = !!objectEquality;
  this.scope = scope;
  this.id = watcherCount++;
}

Watcher.prototype = {
  constructor: Watcher
}

function Scope() {
  this.$id = nextUid();
  this.$$phase = this.$parent = this.$$watchers =
                 this.$$nextSibling = this.$$prevSibling =
                 this.$$childHead = this.$$childTail = null;
  this.$root = this;
  this.$$destroyed = false;
  this.$$listeners = {};
  this.$$listenerCount = {};
  this.$$isolateBindings = null;
  this.childsScopes = [];
}


Scope.prototype = {
  constructor: Scope,

  $new: function(isolate, parent) {
    var child;

    parent = parent || this;

    if (isolate) {
      child = new Scope();
      child.$root = this.$root;
    } else {
      // Only create a child scope class if somebody asks for one,
      // but cache it to allow the VM to optimize lookups.
      if (!this.$$ChildScope) {
        this.$$ChildScope = function ChildScope() {
          this.$$watchers = this.$$nextSibling =
              this.$$childHead = this.$$childTail = null;
          this.$$listeners = {};
          this.$$listenerCount = {};
          this.$id = nextUid();
          this.$$ChildScope = null;
        };
        this.$$ChildScope.prototype = this;
      }
      child = new this.$$ChildScope();
    }
    //window.scopes = window.scopes || {};
    //window.scopes[child.$id] = child;
    this.childsScopes.push(child);
    child.$parent = parent;
    child.$$prevSibling = parent.$$childTail;
    if (parent.$$childHead) {
      parent.$$childTail.$$nextSibling = child;
      parent.$$childTail = child;
    } else {
      parent.$$childHead = parent.$$childTail = child;
    }

    // When the new scope is not isolated or we inherit from `this`, and
    // the parent scope is destroyed, the property `$$destroyed` is inherited
    // prototypically. In all other cases, this property needs to be set
    // when the parent scope is destroyed.
    // The listener needs to be added after the parent is set
    if (isolate || parent != this) child.$on('$destroy', destroyChild);

    return child;

    function destroyChild() {
      child.$$destroyed = true;
      child.$$watchers = null;
      child.$$listeners = {};
      //child.$parent = null;
      child.$$nextSibling = null;
      child.$$childHead = null;
      child.$$childTail = null;
      child.$$prevSibling = null;
      child.$$listenerCount = {};
      if (child.$parent) {
        var index = child.$parent.childsScopes.indexOf(child);
        child.$parent.childsScopes.splice(index, 1);
      }

      console.log("Destroying childScope " + child.$id);

    }
  }

  $destroy: function() {
    // we can't destroy the root scope or a scope that has been already destroyed
    if (this.$$destroyed) return;
    var parent = this.$parent;
    console.log('Destroying Scope '+ this.$id);
    //delete window.scopes[this.$id];
    this.$broadcast('$destroy');
    this.$$destroyed = true;
    if (this === $rootScope) return;

    for (var eventName in this.$$listenerCount) {
      decrementListenerCount(this, this.$$listenerCount[eventName], eventName);
    }

    // sever all the references to parent scopes (after this cleanup, the current scope should
    // not be retained by any of our references and should be eligible for garbage collection)
    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;

    // Disable listeners, watchers and apply/digest methods
    this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
    this.$on = this.$watch = this.$watchGroup = function() { return noop; };
    this.$$listeners = {};

    // All of the code below is bogus code that works around V8's memory leak via optimized code
    // and inline caches.
    //
    // see:
    // - https://code.google.com/p/v8/issues/detail?id=2073#c26
    // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909
    // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451

    this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
        this.$$childTail = this.$root = this.$$watchers = null;
  }


}];
}

答案 1 :(得分:0)

删除缓存的SVG数据。

指令代码

(function () {
    "use strict";

    angular.module('ApplicationModule').directive("ngD3SvgRefreshDirective", ["$rootScope", function ($rootScope) {
        return {
            restrict: 'AE',
            templateUrl: 'pages/directive/D3SvgMyRefereshDirective.html',
            scope: {
                svgData: "=svgData",
                reloadSvg: "=reloadSvg",
            },
            controller: function ($scope, $sce, $rootScope) {
                $scope.$watch('reloadSvg', function (nu, old) {
                    $scope.init();
                }, true);

                $scope.init = function () {
                    if ($scope.svgData && $scope.svgData.layoutURI) {
                        console.log("data");
                        if ($scope.svgData) {
                            // if data is present ###
                            var svgDiv = angular.element(document.querySelector('#svgDiv')).html("");
                            d3.xml($scope.svgData.layoutURI, "image/svg+xml", function (xml) {
                                svgDiv.append(xml.documentElement);
                                $scope.styleSVGPathsNaddCallBack();
                            });
                        }
                    }
                    else {
                        // if data is not present ###
                        var svgDiv = null;
                        // selecting all the svg div which already present in browser ###
                        var svg = d3.select("svg");
                        svg.selectAll("*").remove();
                        console.log(svg);

                    }
                };
                $scope.styleSVGPathsNaddCallBack = function () {
                    var svgObject = d3.select('#svgDiv').select('svg');
                    svgObject.attr("width", "100.0%");
                    var objColor;
                    if (true) {
                        // your requirement logic

                    }
                };
            }
        };
    }]);
})();

控制器

控制器和服务相结合。数据注册控制器功能。

$scope.dataRegisteryForMyDirective = function (data, dataFlag) {
    if (dataFlag) {

        var svgDataMaster = data;
        var svgData = {};
        $scope.svgData = {};
        svgData.layoutURI = data.layoutURI;
        $scope.svgData = angular.copy(svgDataMaster);

        $rootScope.reloadSvg = !$rootScope.reloadSvg;

    }
    else {
        // making svg data empty, so we can execute the else block of directive --> init()  and remove the cached data  ###
        $scope.svgData = {};
        $rootScope.reloadSvg = !$rootScope.reloadSvg;
    }
}  

指令的服务调用和数据注册表调用

$scope.loadSvgByNumber = function (inputNumber) {
    var d = inputNumber;
    myService.getData(d).then(function (resp) {


        if (resp.status == "FAILURE") {
            $scope.svgTempData = [];
            $scope.dataRegisteryForMyDirective(resp, false);
            // calling to dataRegisteryForMyDirective  with false flag 
        }
        else {
            $scope.dataRegisteryForMyDirective(resp, true);
            // calling to dataRegisteryForMyDirective with true flag
        }
    });
};