Wie zähle ich die Gesamtzahl der Uhren auf einer Seite?


143

Gibt es in JavaScript eine Möglichkeit, die Anzahl der eckigen Uhren auf der gesamten Seite zu zählen?

Wir verwenden Batarang , aber es entspricht nicht immer unseren Bedürfnissen. Unsere Anwendung ist groß und wir sind daran interessiert, mithilfe automatisierter Tests zu überprüfen, ob die Anzahl der Uhren zu stark ansteigt.

Es wäre auch nützlich, Uhren pro Controller zu zählen.

Edit : hier ist mein versuch. Es zählt Uhren in allem mit Klasse 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;
})();

Pro Controller ist einfach. Jeder $scopehat ein $$ watchers-Array mit der Anzahl der Watcher auf diesem Controller (wenn Sie eine ng-Wiederholung haben oder etwas, das einen anderen Bereich erstellt, funktioniert das nicht so gut). Aber ich denke, dass es keine Möglichkeit gibt, alle Uhren in der gesamten App zu sehen.
Jesus Rodriguez

Antworten:


219

(Möglicherweise müssen Sie bodyzu htmloder wo immer Sie Ihre ändern 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);
})();
  • Dank an erilem für den Hinweis auf diese Antwort fehlte die $isolateScopeSuche und die Beobachter wurden möglicherweise in seiner Antwort / ihrem Kommentar dupliziert.

  • Vielen Dank an Ben2307 für den Hinweis, dass das 'body'möglicherweise geändert werden muss.


Original

Ich habe das Gleiche getan, außer dass ich das Datenattribut des HTML-Elements und nicht dessen Klasse überprüft habe. Ich habe deine hier geführt:

http://fluid.ie/

Und bekam 83. Ich lief meine und bekam 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);
})();

Ich habe das auch in meine aufgenommen:

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

Und nichts wurde ausgedruckt, also schätze ich, dass meine besser ist (da sie mehr Uhren gefunden hat) - aber mir fehlt intimes Winkelwissen, um sicher zu sein, dass meine keine richtige Teilmenge der Lösungsmenge ist.


2
Ich habe versucht, etwas Ähnliches zu tun, und fand dieses Snippet sehr hilfreich. Ich denke, es lohnt sich zu klären, dass 'root' auf jedes Element gesetzt werden sollte, das das Attribut 'ng-app' hat, da dort das $ rootScope aufbewahrt wird. In meiner App befindet es sich auf dem 'HTML'-Tag. Führen Sie Ihr Skript so aus, wie es den $ watchers in $ rootScope in meiner App fehlt.
Aamiri

Ich habe ein Problem mit dem obigen Code gefunden: Wenn ein untergeordnetes Element einen eigenen Bereich hat, aber jetzt Beobachter, würde nicht $ scope. $$ Beobachter geben die Beobachter des übergeordneten Bereichs zurück? Ich denke, Sie sollten vor dem Push so etwas hinzufügen (ich benutze lodash): if (! _. Enthält (Beobachter, Beobachter)) {watchers.push (Beobachter); }
Ben2307

2
Dadurch werden alle Beobachter abgerufen, die an DOM-Elemente angehängt sind. Wenn Sie ein DOM-Element entfernen, wird das zugehörige $scopeund $$watcherautomatische Element bereinigt , oder tritt ein Leistungseinbruch auf?
SimplGy

Berücksichtigt es die neue einmalige Bindungsfunktion? Überspringt Ihre Zählung diese Art der Bindung?
Systempuntoout

10
Nur ein Hinweis: Wenn Sie $compileProvider.debugInfoEnabled(false);in Ihrem Projekt verwenden, zählt dieses Snippet null Beobachter.
Michael Klöpzig


12

Hier ist eine hackige Lösung, die ich basierend auf der Überprüfung der Scope-Strukturen zusammengestellt habe. Es "scheint" zu funktionieren. Ich bin mir nicht sicher, wie genau dies ist und es hängt definitiv von einer internen API ab. Ich benutze anglejs 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);
    };

Dies ähnelt meiner ursprünglichen Lösung (siehe Bearbeitungsverlauf). Ich bin zu einem anderen Ansatz übergegangen, weil ich denke, dass das Durchlaufen der Bereichshierarchie isolierte Bereiche vermissen würde.
ty.

3
Wenn ich einen isolierten Bereich mit $rootScope.$new(true)oder erstelle $scope.$new(true), wo $scopesich der Controller befindet, wird beim Durchlaufen der Hierarchie dieser Bereich weiterhin gefunden. Ich denke, es bedeutet, dass die Prototypen nicht miteinander verbunden sind, anstatt dass der Umfang nicht in der Hierarchie liegt.
Ian Wilson

Ja, alle Bereiche stammen vom $ rootScope ab, nur die Vererbung ist in isolierten Bereichen "isoliert". In Direktiven werden häufig isolierte Bereiche verwendet. Hier möchten Sie nicht, dass App-Variablen von Eltern stören.
Markmarijnissen

Wenn Sie versuchen, von Ihnen erstellte Bereiche zu erkennen, diese jedoch nicht bereinigen, ist diese Methode überlegen. In meinem Fall zeigt das Crawlen des DOM immer die gleiche Anzahl von Bereichen, aber diejenigen, die nicht an DOM gebunden sind, multiplizieren sich im Schattenland.
SimplGy


9

Da ich kürzlich auch mit einer großen Anzahl von Beobachtern in meiner Anwendung zu kämpfen hatte, fand ich eine großartige Bibliothek namens ng-stats heraus - https://github.com/kentcdodds/ng-stats . Es hat nur minimale Einstellungen und gibt Ihnen die Anzahl der Beobachter auf der aktuellen Seite + Digest-Zykluslänge. Es könnte auch ein kleines Echtzeitdiagramm projizieren.


8

Kleinere Verbesserung für Words Like Jareds Antwort .

(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
Da Sie keine jQuery-Selektoren verwenden, können Sie angular.element () anstelle von $ ()
beardedlinuxgeek

8

In AngularJS 1.3.2 a countWatchers wurde dem ngMock-Modul Methode hinzugefügt:

/ **
 * @ ngdoc-Methode
 * @name $ rootScope.Scope # $ countWatchers
 * @module ngMock
 * @Beschreibung
 * Zählt alle Beobachter direkter und indirekter untergeordneter Bereiche des aktuellen Bereichs.
 * *
 * Die Beobachter des aktuellen Bereichs sind in der Zählung enthalten, ebenso alle Beobachter von
 * Kinderbereiche isolieren.
 * *
 * @returns {number} Gesamtzahl der Beobachter.
 * /

  Funktion countWatchers () 
   {
   var root = angle.element (document) .injector (). get ('$ rootScope');
   var count = root. $$ Beobachter? root. $$ watchers.length: 0; // den aktuellen Bereich einschließen
   var pendingChildHeads = [root. $$ childHead];
   var currentScope;

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

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

   Rückgabezahl;
   }}

Verweise


1
Vielen Dank! Ich wechselte angular.element(document)zu angular.element('[ng-app]')und legte es in ein Lesezeichen mit einer Warnung:alert('found ' + countWatchers() + ' watchers');
undefiniert

4

Ich habe den folgenden Code direkt aus der $digestFunktion selbst übernommen. Natürlich müssen Sie wahrscheinlich den Anwendungselement-Selektor ( document.body) unten aktualisieren .

(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

Dies sind die Funktionen, die ich benutze:

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

})();

Diese Funktion verwendet einen Hash, um denselben Bereich nicht mehrmals zu zählen.


Ich denke, element.data()kann manchmal undefiniert sein oder so (zumindest in einer 1.0.5-Anwendung, bei der ich einen Fehler bekam, als ich diesen Ausschnitt ausführte und versuchte aufzurufen countWatchers). Nur zur Info.
Jared

1

Es gibt eine rekursive Funktion, die von Lars Eidnes 'Blog unter http://larseidnes.com/2014/11/05/angularjs-the-bad-parts/ veröffentlicht wurde , um die Gesamtzahl der Beobachter zu erfassen. Ich vergleiche das Ergebnis mit der hier veröffentlichten Funktion und der in seinem Blog veröffentlichten Funktion, die eine etwas höhere Anzahl generiert hat. Ich kann nicht sagen, welches genauer ist. Gerade hier als Referenz hinzugefügt.

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;

HINWEIS: Möglicherweise müssen Sie "ng-app" für Ihre Angular-App in "data-ng-app" ändern.


1

Plantians Antwort ist schneller: https://stackoverflow.com/a/18539624/258482

Hier ist eine Funktion, die ich von Hand geschrieben habe. Ich habe nicht darüber nachgedacht, rekursive Funktionen zu verwenden, aber das habe ich stattdessen getan. Es könnte schlanker sein, ich weiß es nicht.

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

Legen Sie dies dann in Ihren höchsten Controller (wenn Sie einen globalen Controller verwenden).

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

Und schließlich ein Lesezeichen:

javascript:logScope();

0

Ein bisschen spät zu dieser Frage, aber ich benutze diese

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

Stellen Sie einfach sicher, dass Sie den richtigen querySelector verwenden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.