Come contare il numero totale di orologi su una pagina?


144

Esiste un modo, in JavaScript, per contare il numero di orologi angolari sull'intera pagina?

Usiamo il Batarang , ma non sempre soddisfa i nostri bisogni. La nostra applicazione è grande e siamo interessati all'utilizzo di test automatici per verificare se il conteggio degli orologi aumenta troppo.

Sarebbe anche utile contare gli orologi per controller.

Modifica : ecco il mio tentativo. Conta orologi in tutto con classe 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;
})();

Per controller è facile. Ognuno $scopeha un array di watcher $$ con il numero di watcher su quel controller (beh, se hai qualche ng-repeat o qualcosa che crea un altro ambito, non funziona così bene). Ma penso che non ci sia modo di vedere tutti gli orologi nell'intera app.
Jesus Rodriguez,

Risposte:


220

(Potrebbe essere necessario cambiare bodya htmlo dovunque si mette la 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);
})();
  • Grazie a erilem per aver sottolineato questa risposta mancava la $isolateScopericerca e gli osservatori potevano essere duplicati nella sua risposta / commento.

  • Grazie a Ben2307 per aver sottolineato che 'body'potrebbe essere necessario cambiare.


Originale

Ho fatto la stessa cosa, tranne che ho controllato l'attributo di dati dell'elemento HTML anziché la sua classe. Ho eseguito il tuo qui:

http://fluid.ie/

E ho 83. Ho corso il mio e ho ottenuto 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);
})();

Ho anche messo questo nel mio:

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

E nulla è stato stampato, quindi immagino che il mio sia migliore (in quanto ha trovato più orologi) - ma mi manca una conoscenza angolare intima per sapere con certezza che il mio non è un sottoinsieme corretto del set di soluzioni.


2
Ho cercato di fare qualcosa di simile e ho trovato questo frammento molto utile. Una cosa che penso che valga la pena chiarire è che 'root' dovrebbe essere impostato su qualunque elemento abbia l'attributo 'ng-app', in quanto è qui che viene conservato $ rootScope. Nella mia app è sul tag 'html'. Eseguendo il tuo script come mancano i $ watcher in $ rootScope nella mia app.
aamiri,

Ho riscontrato un problema con il codice sopra: se un elemento figlio ha il proprio ambito, ma ora gli osservatori, non avrebbero $ scope. $$ watchers restituirebbe gli osservatori del scope genitore? Penso che dovresti aggiungere qualcosa prima di questo (sto usando lodash): if (! _. Contiene (watchers, watcher)) {watchers.push (watcher); }
Ben2307,

2
In questo modo tutti gli osservatori collegati agli elementi DOM. Se si rimuove un elemento DOM, si esegue la pulizia del componente associato $scopee $$watcherautomatico o si verifica un impatto sulle prestazioni?
Semplicemente il

Tiene conto della nuova funzionalità di rilegatura singola? Il tuo conto salta questo tipo di rilegatura?
systempuntoout

10
Solo un avvertimento: se usi $compileProvider.debugInfoEnabled(false);nel tuo progetto, questo frammento conterà zero osservatori.
Michael Klöpzig,


12

Ecco una soluzione caotica che ho messo insieme basandomi sul controllo delle strutture dell'ambito. "Sembra" funzionare. Non sono sicuro di quanto sia preciso e dipende sicuramente da alcune API interne. Sto usando 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);
    };

Questo è simile alla mia soluzione originale (vedi cronologia delle modifiche). Mi sono spostato su un approccio diverso perché penso che camminare nella gerarchia degli ambiti mancherebbe isolare gli ambiti.
ty.

3
Se creo un ambito isolato con $rootScope.$new(true)o $scope.$new(true), dove si $scopetrova il controller, allora camminare nella gerarchia trova ancora quell'ambito. Penso che significhi che i prototipi non sono collegati invece che l'ambito non è nella gerarchia.
Ian Wilson,

Sì, tutti gli ambiti discendono da $ rootScope, solo l'ereditarietà è "isolata" in ambiti isolati. Gli ambiti isolati vengono spesso utilizzati nelle direttive: qui non si desidera interferire con le variabili app dei genitori.
markmarijnissen,

Se stai provando a rilevare gli ambiti che crei ma non pulisci, questo metodo è superiore. Nel mio caso, la scansione del DOM mostra sempre lo stesso numero di ambiti, ma quelli che non sono collegati al DOM si moltiplicano in Shadowland.
Semplicemente il


9

Dato che recentemente stavo lottando con un numero elevato di osservatori nella mia applicazione, ho scoperto una grande libreria, chiamata ng-stats - https://github.com/kentcdodds/ng-stats . Ha una configurazione minima e ti dà il numero di osservatori nella pagina corrente + durata del ciclo di digest. Potrebbe anche proiettare un piccolo grafico in tempo reale.


8

Miglioramento minore per Words Come la risposta di 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
Dato che non stai usando i selettori jQuery, puoi usare angular.element () invece di $ ()
beardedlinuxgeek

8

In AngularJS 1.3.2, countWatchersè stato aggiunto un metodo al modulo ngMock:

/ **
 * Metodo @ngdoc
 * @name $ rootScope.Scope # $ countWatchers
 * @module ngMock
 * @description
 * Conta tutti gli osservatori di ambiti figlio diretti e indiretti dell'ambito corrente.
 *
 * Gli osservatori dell'ambito corrente sono inclusi nel conteggio, così come tutti gli osservatori di
 * isolare ambiti figlio.
 *
 * @returns {number} Numero totale di osservatori.
 * /

  funzione countWatchers () 
   {
   var root = angular.element (document) .injector (). get ('$ rootScope');
   var count = root. $$ osservatori? root. $$ watchers.length: 0; // include l'ambito corrente
   var pendingChildHeads = [root. $$ childHead];
   var currentScope;

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

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

   conteggio dei resi;
   }

Riferimenti


1
Grazie! Sono passato angular.element(document)a angular.element('[ng-app]')e l' ho inserito in un bookmarklet con un avviso:alert('found ' + countWatchers() + ' watchers');
indefinito il

4

Ho preso il codice qui sotto direttamente dalla $digestfunzione stessa. Ovviamente, probabilmente dovrai aggiornare l'elemento selettore ( document.body) nella parte inferiore.

(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

Queste sono le funzioni che utilizzo:

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

})();

Questa funzione utilizza un hash per non contare più volte lo stesso ambito.


Penso che a element.data()volte possa essere indefinito o qualcosa del genere (almeno su un'applicazione 1.0.5 su cui ho ricevuto un errore quando ho eseguito questo frammento e ho provato a chiamare countWatchers). Cordiali saluti.
Jared,

1

Esiste una funzione ricorsiva pubblicata dal blog di Lars Eidnes su http://larseidnes.com/2014/11/05/angularjs-the-bad-parts/ per raccogliere il numero totale di osservatori. Confronto il risultato usando la funzione pubblicata qui e quella che ha pubblicato nel suo blog, che ha generato un numero leggermente più alto. Non so dire quale sia più preciso. Appena aggiunto qui come riferimento trasversale.

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;

NOTA: potrebbe essere necessario modificare "ng-app" in "data-ng-app" per la tua app Angular.


1

La risposta di Plantian è più veloce: https://stackoverflow.com/a/18539624/258482

Ecco una funzione che ho scritto a mano. Non ho pensato di usare le funzioni ricorsive, ma è quello che ho fatto invece. Potrebbe essere più magro, non lo so.

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

Quindi inseriscilo nel controller più alto (se usi un controller globale).

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

E infine un bookmarket:

javascript:logScope();

0

Un po 'tardi a questa domanda, ma io uso questo

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

assicurati solo di utilizzare querySelector corretto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.