Как посчитать общее количество часов на странице?

144

Есть ли способ в JavaScript подсчитать количество угловых часов на всей странице?

Мы используем Батаранг , но он не всегда соответствует нашим потребностям. Наше приложение очень большое, и мы заинтересованы в использовании автоматических тестов, чтобы проверить, не увеличилось ли количество часов.

Также было бы полезно подсчитать количество часов для каждого контроллера.

Изменить : вот моя попытка. Считает часы во всем с классом 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 или что-то, что создает другую область видимости, это не работает так хорошо). Но я думаю, что нет возможности увидеть все часы во всем приложении.
Иисус Родригес

Ответы:

220

(Вы , возможно , потребуется изменение bodyв htmlили везде , где вы кладете 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);
})();
  • Спасибо Эрилем за указание на то, что в этом ответе пропущен $isolateScopeпоиск, и наблюдатели потенциально дублируются в его / ее ответе / комментарии.

  • Спасибо Ben2307 за указание на то, что, 'body'возможно, потребуется изменить.


оригинал

Я сделал то же самое, за исключением того, что проверил атрибут данных элемента HTML, а не его класс. Я управлял твоим здесь:

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". Запуск вашего скрипта как пропущен $ watchers в $ rootScope в моем приложении.
аамири
Я обнаружил одну проблему с приведенным выше кодом: если дочерний элемент имеет собственную область видимости, но теперь наблюдатели, не будет ли $ scope. $$ watchers возвращает наблюдатели родительской области действия? Я думаю, вы должны добавить перед отправкой что-то вроде этого (я использую lodash): if (! _. Содержит (наблюдатели, наблюдатель)) {watchers.push (наблюдатель); }
Ben2307
2
Это получает все наблюдатели, которые прикреплены к элементам DOM. Если вы удаляете элемент DOM, происходит ли очистка связанного $scopeи $$watcherавтоматического режима или происходит снижение производительности?
SimplGy
Учитывает ли это новую функцию одноразового связывания? Ваш счет пропускает такую ​​привязку?
systempuntoout
10
Просто наперед: если вы используете $compileProvider.debugInfoEnabled(false);в своем проекте, этот фрагмент будет считать ноль наблюдателей.
Майкл Клёпциг
16

Я думаю, что упомянутые подходы неточны, так как они считают наблюдателей в одной области двойным. Вот моя версия букмарклета:

https://gist.github.com/DTFagus/3966db108a578f2eb00d

Это также показывает некоторые дополнительные детали для анализа наблюдателей.

DTFagus
источник
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);
    };
Ян Уилсон
источник
Это похоже на мое оригинальное решение (см. Историю изменений). Я перешел к другому подходу, потому что думаю, что обход иерархии областей будет пропускать отдельные области.
Ты.
3
Если я создаю изолированную область с помощью $rootScope.$new(true)или $scope.$new(true), где $scopeнаходится для контроллера, то обход иерархии все еще находит эту область. Я думаю, что это означает, что прототипы не связаны, а не находится в иерархии.
Ян Уилсон
Да, все области происходят от $ rootScope, только изолированное наследование «изолировано» в изолированных областях. Изолированные директивы часто используются в директивах - здесь вы не хотите, чтобы переменные приложения от родителей вмешивались.
markmarijnissen
Если вы пытаетесь обнаружить области, которые вы создаете, но не очищаете, этот метод лучше. В моем случае сканирование DOM всегда показывает одинаковое количество областей, но те, которые не подключены к DOM, размножаются в стране теней.
SimplGy
10

Существует новый плагин Chrome, который автоматически отображает текущее общее количество зрителей и последние изменения (+/-) в любое время в вашем приложении ... это просто потрясающе.

https://chrome.google.com/webstore/detail/angular-watchers/nlmjblobloedpmkmmckeehnbfalnjnjk

Ник Стил
источник
это действительно очень удачно
Суджит Й. Кулкарни
9

Поскольку недавно я также боролся с большим количеством наблюдателей в своем приложении, я обнаружил замечательную библиотеку ng-stats - https://github.com/kentcdodds/ng-stats . Он имеет минимальные настройки и дает вам количество наблюдателей на текущей странице + продолжительность цикла дайджеста. Он также может проецировать небольшой график в реальном времени.

boyomarinov
источник
8

Незначительное улучшение слов вроде ответа Джареда .

(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;
})();
Miraage
источник
2
Поскольку вы не используете селекторы jQuery, вы можете использовать angular.element () вместо $ ()
beardedlinuxgeek
8

В AngularJS 1.3.2 countWatchersв модуль ngMock был добавлен метод:

/ **
 * метод @ngdoc
 * @name $ rootScope.Scope # $ countWatchers
 * @module ngMock
 * @описание
 * Подсчитывает все наблюдатели прямых и косвенных дочерних областей текущего объема.
 *
 * Наблюдатели текущей области включены в подсчет, как и все наблюдатели
 * изолировать детские прицелы.
 *
 * @returns {число} Общее количество зрителей.
 * /

  функция countWatchers () 
   {
   var root = angular.element (document) .injector (). get ('$ rootScope');
   var count = root. $$ наблюдатели? root. $$ watchers.length: 0; // включить текущую область
   var pendingChildHeads = [root. $$ childHead];
   var currentScope;

   while (pendingChildHeads.length) 
    {
    currentScope = pendingChildHeads.shift ();

    while (currentScope) 
      {
      count + = currentScope. $$ наблюдатели? currentScope. $$ watchers.length: 0;
      pendingChildHeads.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);
  };

})();

Эта функция использует хеш, чтобы не считать одну и ту же область видимости несколько раз.

erilem
источник
Я думаю, что element.data()иногда может быть неопределенным или что-то в этом роде (по крайней мере, в приложении 1.0.5, в котором я получил ошибку, когда запустил этот фрагмент и попытался вызвать countWatchers). Просто к вашему сведению.
Джаред
1

Существует рекурсивная функция, опубликованная в блоге Ларса Эйднеса по адресу 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;

ПРИМЕЧАНИЕ: вам может потребоваться изменить «ng-app» на «data-ng-app» для вашего приложения Angular.

zd333
источник
1

Плантиан ответ быстрее: 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.

makrigia
источник