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

Jared
quelle
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
16

Ich denke, die genannten Ansätze sind ungenau, da sie Beobachter im gleichen Umfang doppelt zählen. Hier ist meine Version eines Lesezeichens:

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

Es werden auch einige weitere Details zur Analyse von Beobachtern angezeigt.

DTFagus
quelle
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);
    };
Ian Wilson
quelle
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.

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

Paul Sweatte
quelle
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'));

grauer Fuchs
quelle
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.

erilem
quelle
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.

zd333
quelle
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();
Arlen Beiler
quelle
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.

Makrigia
quelle