Як порахувати загальну кількість годин на сторінці?


144

Чи є в JavaScript можливість порахувати кількість кутових годин на всій сторінці?

Ми використовуємо Batarang , але це не завжди відповідає нашим потребам. Наш додаток великий, і ми зацікавлені у використанні автоматизованих тестів, щоб перевірити, чи не надто зростає кількість годин.

Було б також корисно порахувати годинник на основі контролера.

Редагувати : ось моя спроба. Він нараховує годинник у всьому, що має клас ng-range.

(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має набір $$ спостерігачів із кількістю спостерігачів на цьому контролері (добре, якщо у вас є ng-повторення або щось, що створює інший обсяг, це не дуже добре). Але я думаю, що немає можливості побачити всі годинники у всьому додатку.
Ісус Родрігес

Відповіді:


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'. Запуск вашого сценарію, як це пропущено $ спостерігачів у $ rootScope в моєму додатку.
аамірі

Я виявив одну проблему з вищевказаним кодом: якщо дочірній елемент має власну сферу застосування, але тепер спостерігачі не отримали б $ range. $$ watchers повертають це спостерігачі батьківської області? Я думаю, ви повинні додати перед натиском щось подібне (я використовую lodash): якщо (! _. Містить (watchers, watcher)) {watchers.push (watcher); }
Ben2307

2
Це отримує всіх спостерігачів, які прикріплені до елементів DOM. Якщо ви видалите елемент DOM, чи відбувається очищення пов’язаних $scopeта $$watcherавтоматичних, чи відбувається враження від продуктивності?
SimplGy

Чи враховує вона нову функцію одноразового зв’язування? Чи пропускає ваш граф такий прив'язок?
systempuntoout

10
Лише вгору: якщо ви використовуєте $compileProvider.debugInfoEnabled(false);у своєму проекті, цей фрагмент буде рахувати нуль спостерігачів.
Майкл Клопзіг

16

Я вважаю, що згадані підходи є неточними, оскільки вони нараховують спостерігачів в однаковій мірі вдвічі. Ось моя версія закладок:

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

Тут також показано ще кілька деталей для аналізу спостерігачів.


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


9

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


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;
})();

2
Оскільки ви не використовуєте селектори jQuery, ви можете використовувати angular.element () замість $ ()
beardedlinuxgeek

8

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

/ **
 * @ngdoc метод
 * @name $ rootScope.Scope # $ countWatchers
 * @модуль ngMock
 * @опис
 * Підраховує всіх спостерігачів прямих і непрямих діапазонів дітей поточної сфери.
 *
 * Спостерігачі поточного розряду включаються до складу підрахунку, а також всі спостерігачі
 * виділити області застосування дітей.
 *
 * @returns {number} Загальна кількість спостерігачів.
 * /

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

   в той час, як (очікує на довжину. 
    {
    currentScope = pendingChildHeads.shift ();

    в той час (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');
не визначене

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). Просто FYI.
Джаред

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.


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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.