Como contar o número total de relógios em uma página?

144

Existe uma maneira, em JavaScript, de contar o número de relógios angulares em toda a página?

Usamos Batarang , mas nem sempre atende às nossas necessidades. Nossa aplicação é grande e estamos interessados ​​em usar testes automatizados para verificar se a contagem de visualizações aumenta demais.

Também seria útil contar relógios em uma base por controlador.

Edit : aqui está a minha tentativa. Conta relógios em tudo com 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;
})();
ty.
fonte
Por controlador é fácil. Cada $scopeum possui uma matriz de observadores $$ com o número de observadores nesse controlador (bem, se você tiver algum ng-repeat ou algo que crie outro escopo, que não funcione tão bem). Mas acho que não há como ver todos os relógios em todo o aplicativo.
Jesus Rodriguez

Respostas:

220

(Pode ser necessário mudar bodypara htmlou onde quer que você coloque 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);
})();
  • Obrigado a erilem por apontar que esta resposta estava faltando na $isolateScopepesquisa e os observadores estavam potencialmente duplicados em sua resposta / comentário.

  • Agradecemos a Ben2307 por apontar que 'body'talvez seja necessário alterar.


Original

Fiz a mesma coisa, exceto que verifiquei o atributo de dados do elemento HTML em vez de sua classe. Eu corri o seu aqui:

http://fluid.ie/

E tenho 83. Corri o meu e tenho 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);
})();

Eu também coloquei isso no meu:

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 nada foi impresso, então acho que o meu é melhor (na medida em que encontrou mais relógios) - mas não tenho conhecimento angular íntimo para ter certeza de que o meu não é um subconjunto adequado do conjunto de soluções.

Jared
fonte
2
Eu tenho tentado fazer algo semelhante, e achei este trecho muito útil. Uma coisa que acho que vale a pena esclarecer é que 'root' deve ser definido como qualquer elemento que tenha o atributo 'ng-app', pois é onde o $ rootScope é mantido. No meu aplicativo, está na tag 'html'. A execução do seu script está faltando os $ watchers no $ rootScope no meu aplicativo.
aamiri 12/12
Eu encontrei um problema com o código acima: se um elemento filho tem seu próprio escopo, mas agora os observadores não usariam $ scope. $$ watchers retornam seus observadores do escopo pai? Acho que você deve adicionar antes do push algo assim (estou usando o lodash): if (! _. Contains (watchers, watcher)) {watchers.push (watcher); }
Ben2307 16/12/2013
2
Isso obtém todos os observadores anexados aos elementos DOM. Se você remover um elemento DOM, é a limpeza do associado $scopee do $$watcherautomático, ou há um impacto no desempenho em que eles ocorrem?
SimplGy 21/03
Ele leva em consideração o novo recurso de ligação única? Sua contagem ignora esse tipo de ligação?
systempuntoout
10
Apenas um alerta: se você usar $compileProvider.debugInfoEnabled(false);em seu projeto, esse snippet contará zero observadores.
Michael Klöpzig
16

Eu acho que as abordagens mencionadas são imprecisas, pois contam o número de observadores no mesmo escopo. Aqui está minha versão de um bookmarklet:

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

Também mostra mais alguns detalhes para analisar observadores.

DTFagus
fonte
12

Aqui está uma solução hacky que eu montei com base na inspeção das estruturas de escopo. Parece "funcionar". Não sei ao certo se isso é preciso e, definitivamente, depende de alguma API interna. Estou usando o 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);
    };
Ian Wilson
fonte
Isso é semelhante à minha solução original (consulte o histórico de edições). Eu mudei para uma abordagem diferente, porque acho que caminhar na hierarquia do escopo deixaria de isolar os escopos.
ty.
3
Se eu criar um escopo isolado com $rootScope.$new(true)ou $scope.$new(true), onde $scopeé o controlador, a caminhada na hierarquia ainda o encontrará. Eu acho que isso significa que os protótipos não estão conectados, e que o escopo não está na hierarquia.
Ian Wilson
Sim, todos os escopos descendem do $ rootScope, apenas a herança é "isolada" em escopos isolados. Escopos isolados são freqüentemente usados ​​em diretivas - aqui você não deseja que variáveis ​​de aplicativos dos pais interfiram.
Marque
Se você está tentando detectar escopos criados, mas não faz a limpeza, esse método é superior. No meu caso, rastrear o DOM sempre mostra o mesmo número de escopos, mas aqueles que não são anexados ao DOM estão se multiplicando na terra das sombras.
SimplGy 21/03
9

Como recentemente também estava enfrentando um grande número de observadores em meu aplicativo, descobri uma ótima biblioteca chamada ng-stats - https://github.com/kentcdodds/ng-stats . Possui configuração mínima e fornece o número de observadores na página atual + duração do ciclo de resumo. Também poderia projetar um pequeno gráfico em tempo real.

boyomarinov
fonte
8

Pequena melhoria para a resposta de Words Like 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;
})();
Miraage
fonte
2
Porque você não está usando seletores jQuery, você pode usar angular.element () em vez de $ ()
beardedlinuxgeek
8

No AngularJS 1.3.2, um countWatchersmétodo foi adicionado ao módulo ngMock:

/ **
 * método @ngdoc
 * @name $ rootScope.Scope # $ countWatchers
 * @module ngMock
 * @descrição
 * Conta todos os observadores de escopos filho diretos e indiretos do escopo atual.
 *
 * Os observadores do escopo atual estão incluídos na contagem e todos os observadores de
 * isolar escopos filho.
 *
 * @returns {number} Número total de observadores.
 * /

  função countWatchers () 
   {
   var root = angular.element (document) .injector (). get ('$ rootScope');
   var count = root. $$ observadores? root. $$ watchers.length: 0; // inclui o escopo atual
   var pendingChildHeads = [root. $$ childHead];
   var currentScope;

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

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

   contagem de retorno;
   }

Referências

Paul Sweatte
fonte
1
Obrigado! Mudei angular.element(document)para angular.element('[ng-app]')e coloquei em um bookmarklet com um alerta:alert('found ' + countWatchers() + ' watchers');
undefined
4

Peguei o código abaixo diretamente da $digestprópria função. Obviamente, você provavelmente precisará atualizar o seletor de elementos do aplicativo ( document.body) na parte inferior.

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

Raposa-cinzenta
fonte
1

Estas são as funções que eu uso:

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

})();

Esta função usa um hash para não contar o mesmo escopo várias vezes.

erilem
fonte
element.data()Às vezes, acho que pode ser indefinido ou algo assim (pelo menos em um aplicativo 1.0.5 em que ocorreu um erro quando executei esse snippet e tentei ligar countWatchers). Apenas para sua informação.
Jared
1

Há uma função recursiva publicada pelo blog de Lars Eidnes em http://larseidnes.com/2014/11/05/angularjs-the-bad-parts/ para coletar o número total de observadores. Comparo o resultado usando a função postada aqui e a que ele postou em seu blog, que gerou um número um pouco maior. Não sei dizer qual é mais preciso. Apenas adicionado aqui como uma referência geral.

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: pode ser necessário alterar "ng-app" para "data-ng-app" no seu aplicativo Angular.

zd333
fonte
1

A resposta de Plantian é mais rápida: https://stackoverflow.com/a/18539624/258482

Aqui está uma função que eu escrevi à mão. Não pensei em usar funções recursivas, mas foi o que fiz. Pode ser mais enxuto, não sei.

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

Em seguida, coloque isso dentro do seu controlador mais alto (se você usar um controlador global).

$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 finalmente um mercado de livros:

javascript:logScope();
Arlen Beiler
fonte
0

Um pouco tarde para esta pergunta, mas eu uso isso

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

apenas certifique-se de usar o querySelector correto.

makrigia
fonte