如何计算页面上的手表总数?


144

在JavaScript中,有没有一种方法可以计算整个页面上有角度的手表的数量?

我们使用Batarang,但它并不总是适合我们的需求。我们的应用程序很大,我们对使用自动化测试来检查手表计数是否增加过多感兴趣。

在每个控制器的基础上计数手表也将很有用。

编辑:这是我的尝试。它在ng-scope类中将手表计入所有内容。

(function () {
    var elts = document.getElementsByClassName('ng-scope');
    var watches = [];
    var visited_ids = {};
    for (var i=0; i < elts.length; i++) {
       var scope = angular.element(elts[i]).scope();
       if (scope.$id in visited_ids) 
         continue;
       visited_ids[scope.$id] = true;
       watches.push.apply(watches, scope.$$watchers);
    }
    return watches.length;
})();

每个控制器都很容易。每个人$scope都有一个$$ watchers数组,其中包含该控制器上的观察者数量(好吧,如果您有一些ng-repeat或创建另一个作用域的东西,那么效果就不好了)。但我认为无法查看整个应用程序中的所有手表。
耶稣罗德里格斯

Answers:


220

(您可能需要更改bodyhtml或放置在任何位置ng-app

(function () { 
    var root = angular.element(document.getElementsByTagName('body'));

    var watchers = [];

    var f = function (element) {
        angular.forEach(['$scope', '$isolateScope'], function (scopeProperty) { 
            if (element.data() && element.data().hasOwnProperty(scopeProperty)) {
                angular.forEach(element.data()[scopeProperty].$$watchers, function (watcher) {
                    watchers.push(watcher);
                });
            }
        });

        angular.forEach(element.children(), function (childElement) {
            f(angular.element(childElement));
        });
    };

    f(root);

    // Remove duplicate watchers
    var watchersWithoutDuplicates = [];
    angular.forEach(watchers, function(item) {
        if(watchersWithoutDuplicates.indexOf(item) < 0) {
             watchersWithoutDuplicates.push(item);
        }
    });

    console.log(watchersWithoutDuplicates.length);
})();
  • 感谢erilem指出此答案缺少$isolateScope搜索,并且观察者可能在他/她的答案/评论中重复。

  • 感谢Ben2307指出'body'可能需要更改。


原版的

除了检查HTML元素的data属性而不是其类之外,我执行了相同的操作。我在这里跑了你的:

http://fluid.ie/

拿到了83。我跑了我,拿到了121。

(function () { 
    var root = $(document.getElementsByTagName('body'));
    var watchers = [];

    var f = function (element) {
        if (element.data().hasOwnProperty('$scope')) {
            angular.forEach(element.data().$scope.$$watchers, function (watcher) {
                watchers.push(watcher);
            });
        }

        angular.forEach(element.children(), function (childElement) {
            f($(childElement));
        });
    };

    f(root);

    console.log(watchers.length);
})();

我也把这个放在我的:

for (var i = 0; i < watchers.length; i++) {
    for (var j = 0; j < watchers.length; j++) {
        if (i !== j && watchers[i] === watchers[j]) {
            console.log('here');
        }
    }
}

而且没有打印出来,所以我猜我的更好(因为它找到了更多的手表)-但是我缺乏深入的角度知识来确定我的不是解决方案集的适当子集。


2
我一直在尝试做类似的事情,发现此片段非常有用。我认为需要澄清的一件事是,应将“ root”设置为具有“ ng-app”属性的任何元素,因为这是保留$ rootScope的位置。在我的应用程序中,其位于“ html”标签上。像在我的应用程序中$ rootScope中的$ watchers错过了那样运行脚本。
aamiri 2013年

我发现上面的代码有一个问题:如果子元素具有它自己的作用域,但现在是观察者,$ scope不会。$$ watchers返回它是父作用域的观察者吗?我认为您应该在推送之前添加如下内容(我正在使用lodash):if(!_。contains(watchers,watcher)){watchers.push(watcher); }
Ben2307

2
这将获取附加到DOM元素的所有观察者。如果删除DOM元素,是清除关联的元素$scope$$watcher自动元素,还是会导致性能下降?
SimplGy 2014年

是否考虑到新的一次性绑定功能?您的伯爵会跳过这种约束吗?
systempuntoout 2014年

10
请注意:如果$compileProvider.debugInfoEnabled(false);在您的项目中使用,此代码段将计为零观察者。
MichaelKlöpzig15年


12

这是我在检查范围结构的基础上放在一起的一个骇人解决方案。它“似乎”起作用。我不确定这有多准确,它肯定取决于某些内部API。我正在使用angularjs 1.0.5。

    $rootScope.countWatchers = function () {
        var q = [$rootScope], watchers = 0, scope;
        while (q.length > 0) {
            scope = q.pop();
            if (scope.$$watchers) {
                watchers += scope.$$watchers.length;
            }
            if (scope.$$childHead) {
                q.push(scope.$$childHead);
            }
            if (scope.$$nextSibling) {
                q.push(scope.$$nextSibling);
            }
        }
        window.console.log(watchers);
    };

这类似于我的原始解决方案(请参阅编辑历史记录)。我转而使用另一种方法,因为我认为遍历作用域层次结构会错过隔离作用域。
ty。

3
如果我创建一个分离范围相$rootScope.$new(true)$scope.$new(true),其中$scope是为所述控制器,然后走层次结构仍然认定的范围。我认为这意味着原型未连接,而不是范围不在层次结构中。
伊恩·威尔逊

是的,所有作用域都来自$ rootScope,只有继承是“隔离”的。隔离范围通常用于指令中-在这里,您不希望父母的应用程序变量产生干扰。
markmarijnissen 2014年

如果您尝试检测自己创建的作用域但不清理,则此方法更好。以我为例,对DOM进行爬网总是显示相同数量的作用域,但是未与DOM关联的作用域则在阴影域中成倍增加。
SimplGy 2014年


9

最近,在我的应用程序中也遇到大量观察者时,我发现了一个很棒的库,叫做ng-stats - https://github.com/kentcdodds/ng-stats。它的设置最少,可为您提供当前页面上的观察者数量+摘要周期长度。它还可以投影一个小的实时图形。


8

像贾里德(Jared)的答案这样的单词有轻微改进。

(function () {
    var root = $(document.getElementsByTagName('body'));
    var watchers = 0;

    var f = function (element) {
        if (element.data().hasOwnProperty('$scope')) {
            watchers += (element.data().$scope.$$watchers || []).length;
        }

        angular.forEach(element.children(), function (childElement) {
            f($(childElement));
        });
    };

    f(root);

    return watchers;
})();

2
因为您没有使用jQuery选择器,所以可以使用angular.element()代替$()
beardedlinuxgeek 2014年

8

在AngularJS 1.3.2中,countWatchers向ngMock模块添加了一个方法:

/ **
 * @ngdoc方法
 * @name $ rootScope.Scope#$ countWatchers
 * @模块ngMock
 * @说明
 *计算当前范围的所有直接和间接子范围的观察者。
 *
 *当前范围的观察者也包括在内,所有的观察者也包括在内
 *隔离子范围。
 *
 * @returns {number}观察者总数。
 * /

  函数countWatchers() 
   {
   var root = angular.element(document).injector()。get('$ rootScope');
   var count = root。$$ watchers?root。$$ watchers.length:0; //包含当前范围
   varendingChildHeads = [root。$$ childHead];
   var currentScope;

   而(pendingChildHeads.length) 
    {
    currentScope = endingChildHeads.shift();

    while(currentScope) 
      {
      count + = currentScope。$$ watchers?currentScope。$$ watchers.length:0;
      endingChildHeads.push(currentScope。$$ childHead);
      currentScope = currentScope。$$ nextSibling;
      }
    }

   返回计数;
   }

参考资料


1
谢谢!我更改angular.element(document)angular.element('[ng-app]'),并将其放在带有警报的书签中:alert('found ' + countWatchers() + ' watchers');
undefined

4

我直接从$digest函数本身获取以下代码。当然,您可能需要更新document.body底部的应用程序元素选择器()。

(function ($rootScope) {
    var watchers, length, target, next, count = 0;

    var current = target = $rootScope;

    do {
        if ((watchers = current.$$watchers)) {
            count += watchers.length;
        }

        if (!(next = (current.$$childHead ||
                (current !== target && current.$$nextSibling)))) {
            while (current !== target && !(next = current.$$nextSibling)) {
                current = current.$parent;
            }
        }
    } while ((current = next));

    return count;
})(angular.element(document.body).injector().get('$rootScope'));


1

这是我使用的功能:

/**
 * @fileoverview This script provides a window.countWatchers function that
 * the number of Angular watchers in the page.
 *
 * You can do `countWatchers()` in a console to know the current number of
 * watchers.
 *
 * To display the number of watchers every 5 seconds in the console:
 *
 * setInterval(function(){console.log(countWatchers())}, 5000);
 */
(function () {

  var root = angular.element(document.getElementsByTagName('body'));

  var countWatchers_ = function(element, scopes, count) {
    var scope;
    scope = element.data().$scope;
    if (scope && !(scope.$id in scopes)) {
      scopes[scope.$id] = true;
      if (scope.$$watchers) {
        count += scope.$$watchers.length;
      }
    }
    scope = element.data().$isolateScope;
    if (scope && !(scope.$id in scopes)) {
      scopes[scope.$id] = true;
      if (scope.$$watchers) {
        count += scope.$$watchers.length;
      }
    }
    angular.forEach(element.children(), function (child) {
      count = countWatchers_(angular.element(child), scopes, count);
    });
    return count;
  };

  window.countWatchers = function() {
    return countWatchers_(root, {}, 0);
  };

})();

此函数使用哈希表不对同一作用域进行多次计数。


我认为element.data()有时可能是未定义的(至少在1.0.5应用程序中,当我运行此代码并试图调用时我遇到了错误countWatchers)。仅供参考。
Jared 2014年

1

Lars Eidnes的博客在http://larseidnes.com/2014/11/05/angularjs-the-bad-parts/上发布了一个递归函数,用于收集观察者总数。我使用此处发布的功能与他在博客中发布的功能进行比较,生成的数字略高。我不知道哪个更准确。刚刚在这里添加为交叉参考。

function getScopes(root) {
    var scopes = [];
    function traverse(scope) {
        scopes.push(scope);
        if (scope.$$nextSibling)
            traverse(scope.$$nextSibling);
        if (scope.$$childHead)
            traverse(scope.$$childHead);
    }
    traverse(root);
    return scopes;
}
var rootScope = angular.element(document.querySelectorAll("[ng-app]")).scope();
var scopes = getScopes(rootScope);
var watcherLists = scopes.map(function(s) { return s.$$watchers; });
_.uniq(_.flatten(watcherLists)).length;

注意:您可能需要将Angular应用的“ ng-app”更改为“ data-ng-app”。


1

Plantian的答案更快:https ://stackoverflow.com/a/18539624/258482

这是我手工编写的功能。我没有考虑使用递归函数,但这是我所做的。我不知道它可能更精简。

var logScope; //put this somewhere in a global piece of code

然后将其放入最高控制器中(如果使用全局控制器)。

$scope.$on('logScope', function () { 
    var target = $scope.$parent, current = target, next;
    var count = 0;
    var count1 = 0;
    var checks = {};
    while(count1 < 10000){ //to prevent infinite loops, just in case
        count1++;
        if(current.$$watchers)
            count += current.$$watchers.length;

        //This if...else is also to prevent infinite loops. 
        //The while loop could be set to true.
        if(!checks[current.$id]) checks[current.$id] = true;
        else { console.error('bad', current.$id, current); break; }
        if(current.$$childHead) 
            current = current.$$childHead;
        else if(current.$$nextSibling)
            current = current.$$nextSibling;
        else if(current.$parent) {
            while(!current.$$nextSibling && current.$parent) current = current.$parent;
            if(current.$$nextSibling) current = current.$$nextSibling;
            else break;
        } else break;
    }
    //sort of by accident, count1 contains the number of scopes.
    console.log('watchers', count, count1);
    console.log('globalCtrl', $scope); 
   });

logScope = function () {
    $scope.$broadcast('logScope');
};

最后是书市:

javascript:logScope();

0

这个问题有点晚了,但是我用这个

angular.element(document.querySelector('[data-ng-app]')).scope().$$watchersCount

只要确保您使用正确的querySelector。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.