Jak policzyć całkowitą liczbę zegarków na stronie?


144

Czy w JavaScript można policzyć liczbę zegarków kątowych na całej stronie?

Używamy Batarangu , ale nie zawsze odpowiada to naszym potrzebom. Nasza aplikacja jest duża i jesteśmy zainteresowani wykorzystaniem testów automatycznych, aby sprawdzić, czy liczba zegarków nie rośnie zbytnio.

Przydatne byłoby również liczenie zegarków na podstawie liczby kontrolerów.

Edycja : oto moja próba. Liczy zegarki we wszystkim z klasą 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;
})();

Każdy kontroler jest łatwy. Każdy $scopema tablicę obserwatorów $$ z liczbą obserwatorów na tym kontrolerze (cóż, jeśli masz jakieś powtórzenie ng lub coś, co tworzy inny zakres, to nie działa tak dobrze). Ale myślę, że nie ma sposobu, aby zobaczyć wszystkie zegarki w całej aplikacji.
Jesus Rodriguez,

Odpowiedzi:


220

(Może zajść potrzeba zmiany bodyna htmllub w dowolnym miejscu 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);
})();
  • Dzięki Erilemowi za wskazanie tej odpowiedzi brakowało $isolateScopewyszukiwania, a obserwatorzy potencjalnie zostali zduplikowani w swojej odpowiedzi / komentarzu.

  • Podziękowania dla Ben2307 za wskazanie, że 'body'może to wymagać zmiany.


Oryginalny

Zrobiłem to samo, ale sprawdziłem atrybut danych elementu HTML, a nie jego klasę. Twój sprawdziłem tutaj:

http://fluid.ie/

I dostałem 83. Sprawdziłem swój i uzyskałem 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);
})();

Umieściłem też to w swoim:

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

I nic nie zostało wydrukowane, więc domyślam się, że mój jest lepszy (w tym, że znalazł więcej zegarków) - ale brakuje mi dogłębnej wiedzy o kątach, aby wiedzieć na pewno, że mój nie jest właściwym podzbiorem zestawu rozwiązań.


2
Próbowałem zrobić coś podobnego i okazało się, że ten fragment jest bardzo pomocny. Jedną z rzeczy, które moim zdaniem warto wyjaśnić, jest to, że 'root' powinno być ustawione na dowolny element, który ma atrybut 'ng-app', ponieważ jest to miejsce, w którym przechowywany jest $ rootScope. W mojej aplikacji jest na tagu „html”. Uruchamianie skryptu bez widocznych obserwatorów $ w $ rootScope w mojej aplikacji.
aamiri

Znalazłem jeden problem z powyższym kodem: jeśli element podrzędny ma swój własny zasięg, ale teraz obserwatorzy, czy nie $ scope. $$ watchers zwraca obserwatory swojego zakresu nadrzędnego? Myślę, że przed wypchnięciem powinieneś dodać coś takiego (ja używam lodash): if (! _. Zawiera (watchers, watcher)) {watchers.push (watcher); }
Ben2307,

2
Spowoduje to pobranie wszystkich obserwatorów dołączonych do elementów DOM. Jeśli usuniesz element DOM, czy czyszczenie powiązanego $scopei $$watcherautomatycznego elementu jest czyszczone , czy też występuje spadek wydajności, który powoduje?
SimplGy

Czy uwzględnia nową funkcję jednorazowego wiązania? Czy twój hrabia pomija ten rodzaj wiązania?
systempuntoout

10
Uwaga: jeśli użyjesz $compileProvider.debugInfoEnabled(false);w swoim projekcie, ten fragment będzie liczyć zero obserwatorów.
Michael Klöpzig


12

Oto hackerskie rozwiązanie, które stworzyłem na podstawie inspekcji struktur zakresu. Wydaje się, że działa. Nie jestem pewien, jak dokładne jest to i na pewno zależy to od jakiegoś wewnętrznego interfejsu API. Używam 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);
    };

Jest to podobne do mojego oryginalnego rozwiązania (zobacz historię edycji). Przeszedłem do innego podejścia, ponieważ myślę, że chodzenie po hierarchii zakresów pomijałoby zakresy izolowane.
ty.

3
Jeśli utworzę zakres izolowany za pomocą $rootScope.$new(true)lub $scope.$new(true), gdzie $scopejest dla kontrolera, przechodzenie po hierarchii nadal znajduje ten zakres. Myślę, że oznacza to, że prototypy nie są połączone zamiast tego, że zakres nie znajduje się w hierarchii.
Ian Wilson

Tak, wszystkie zasięgi pochodzą od $ rootScope, tylko dziedziczenie jest „izolowane” w izolowanych zasięgach. Izolowane zakresy są często używane w dyrektywach - tutaj nie chcesz, aby zmienne aplikacji od rodziców przeszkadzały.
markmarijnissen

Jeśli próbujesz wykryć tworzone przez siebie zakresy, ale ich nie czyścisz, ta metoda jest lepsza. W moim przypadku indeksowanie DOM zawsze pokazuje tę samą liczbę zakresów, ale te, które nie są dołączone do DOM, mnożą się w krainie cieni.
SimplGy,


9

Ponieważ ostatnio zmagałem się z dużą liczbą obserwatorów w mojej aplikacji, odkryłem świetną bibliotekę o nazwie ng-stats - https://github.com/kentcdodds/ng-stats . Ma minimalną konfigurację i podaje liczbę obserwatorów na bieżącej stronie + długość cyklu podsumowania. Może również wyświetlać mały wykres w czasie rzeczywistym.


8

Niewielkie ulepszenie odpowiedzi Słów takich jak 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
Ponieważ nie używasz selektorów jQuery, możesz użyć angular.element () zamiast $ ()
beardedlinuxgeek

8

W AngularJS 1.3.2 countWatchersdo modułu ngMock dodano metodę:

/ **
 * Metoda @ngdoc
 * @name $ rootScope.Scope # $ countWatchers
 * @module ngMock
 * @description
 * Zlicza wszystkich obserwatorów bezpośrednich i pośrednich zakresów podrzędnych bieżącego zakresu.
 *
 * Strażnicy z obecnego zakresu są wliczani do liczby, podobnie jak wszyscy obserwatorzy
 * izoluj zakresy podrzędne.
 *
 * @returns {number} Łączna liczba obserwatorów.
 * /

  funkcja countWatchers () 
   {
   var root = angular.element (dokument) .injector (). get ('$ rootScope');
   var count = root. $$ obserwatorzy? root. $$ watchers.length: 0; // dołącz aktualny zakres
   var pendingChildHeads = [root. $$ childHead];
   var currentScope;

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

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

   liczba zwrotów;
   }

Bibliografia


1
Dzięki! Zmieniłem angular.element(document)na angular.element('[ng-app]')i umieściłem go w bookmarkletu z ostrzeżeniem:alert('found ' + countWatchers() + ' watchers');
undefined

4

Poniższy kod wziąłem bezpośrednio z $digestsamej funkcji. Oczywiście prawdopodobnie będziesz musiał zaktualizować selektor elementu aplikacji ( document.body) na dole.

(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

Oto funkcje, których używam:

/**
 * @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);
  };

})();

Ta funkcja używa skrótu, aby nie liczyć wielokrotnie tego samego zakresu.


Myślę, że element.data()czasami może być niezdefiniowany lub coś w tym stylu (przynajmniej w aplikacji 1.0.5, w której wystąpił błąd, gdy uruchomiłem to wycięte i próbowałem wywołać countWatchers). Po prostu FYI.
Jared

1

Istnieje funkcja rekurencyjna opublikowana na blogu Larsa Eidnesa pod adresem http://larseidnes.com/2014/11/05/angularjs-the-bad-parts/, która zbiera całkowitą liczbę obserwatorów. Porównuję wynik za pomocą zamieszczonej tutaj funkcji i tej, którą zamieścił na swoim blogu, który wygenerował nieco większą liczbę. Nie mogę powiedzieć, który z nich jest dokładniejszy. Właśnie dodane tutaj jako odniesienie.

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;

UWAGA: możesz potrzebować zmienić „ng-app” na „data-ng-app” dla swojej aplikacji Angular.


1

Odpowiedź Plantiana jest szybsza: https://stackoverflow.com/a/18539624/258482

Oto funkcja, którą napisałem ręcznie. Nie myślałem o użyciu funkcji rekurencyjnych, ale zamiast tego zrobiłem to. Może być szczuplejsza, nie wiem.

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

Następnie umieść to w swoim najwyższym kontrolerze (jeśli używasz kontrolera globalnego).

$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');
};

I wreszcie zakładka:

javascript:logScope();

0

Trochę za późno na to pytanie, ale używam tego

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

po prostu upewnij się, że używasz poprawnego querySelector.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.