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

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.


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


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

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.


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

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


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


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.


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.


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

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.