¿Cómo contar el número total de relojes en una página?

144

¿Hay alguna manera, en JavaScript, de contar el número de relojes angulares en toda la página?

Usamos Batarang , pero no siempre se adapta a nuestras necesidades. Nuestra aplicación es grande y estamos interesados ​​en utilizar pruebas automatizadas para verificar si el recuento de relojes aumenta demasiado.

También sería útil contar los relojes por controlador.

Editar : aquí está mi intento. Cuenta relojes en todo con clase 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.
fuente
Por controlador es fácil. Cada uno $scopetiene una matriz $$ watchers con el número de observadores en ese controlador (bueno, si tienes alguna repetición ng o algo que crea otro alcance, eso no funciona tan bien). Pero creo que no hay forma de ver todos los relojes en toda la aplicación.
Jesús Rodríguez

Respuestas:

220

(Es posible que deba cambiar bodya htmlo donde sea que coloque su 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);
})();
  • Gracias a erilem por señalar que esta respuesta no se realizó la $isolateScopebúsqueda y los observadores posiblemente se duplicaron en su respuesta / comentario.

  • Gracias a Ben2307 por señalar que es 'body'posible que sea necesario cambiarlo.


Original

Hice lo mismo, excepto que verifiqué el atributo de datos del elemento HTML en lugar de su clase. Corrí el tuyo aquí:

http://fluid.ie/

Y obtuve 83. Corrí el mío y obtuve 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);
})();

También puse esto en el mío:

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

Y no se imprimió nada, así que supongo que el mío es mejor (en el sentido de que encontró más relojes), pero me falta un conocimiento angular íntimo para saber con certeza que el mío no es un subconjunto adecuado del conjunto de soluciones.

Jared
fuente
2
He estado tratando de hacer algo similar, y encontré este fragmento muy útil. Una cosa que creo que vale la pena aclarar es que 'root' debe establecerse en cualquier elemento que tenga el atributo 'ng-app', ya que es donde se guarda el $ rootScope. En mi aplicación está en la etiqueta 'html'. Ejecutando su script como se perdió los $ watchers en $ rootScope en mi aplicación.
aamiri
Encontré un problema con el código anterior: si un elemento hijo tiene su propio alcance, pero ahora los observadores, ¿no $ alcance. $$ los observadores devuelven los observadores del alcance principal? Creo que debería agregar antes del empuje algo como esto (estoy usando lodash): if (! _. Contiene (vigilantes, vigilante)) {watchers.push (vigilante); }
Ben2307
2
Esto obtiene todos los observadores que están conectados a elementos DOM. Si elimina un elemento DOM, ¿es la limpieza de lo asociado $scopey $$watcherautomática, o hay un impacto en el rendimiento en el que incurren?
SimplGy
¿Tiene en cuenta la nueva función de enlace único? ¿Su cuenta omite este tipo de encuadernación?
systempuntoout
10
Solo un aviso: si lo usa $compileProvider.debugInfoEnabled(false);en su proyecto, este fragmento contará cero observadores.
Michael Klöpzig
16

Creo que los enfoques mencionados son inexactos ya que cuentan a los observadores en el mismo alcance doble. Aquí está mi versión de un marcador:

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

También muestra algunos detalles más para analizar a los observadores.

DTFagus
fuente
12

Aquí hay una solución hacky que armé en base a la inspección de las estructuras del alcance. Parece funcionar. No estoy seguro de cuán preciso es esto y definitivamente depende de alguna API interna. Estoy 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);
    };
Ian Wilson
fuente
Esto es similar a mi solución original (ver historial de edición). Me moví a un enfoque diferente porque creo que caminar por la jerarquía del alcance omitiría los ámbitos de aislamiento.
ty.
3
Si creo un ámbito aislado con $rootScope.$new(true)o $scope.$new(true), donde $scopees para el controlador, entonces caminar por la jerarquía aún encuentra ese ámbito. Creo que significa que los prototipos no están conectados en lugar de que el alcance no esté en la jerarquía.
Ian Wilson el
Sí, todos los ámbitos descienden del $ rootScope, solo la herencia está "aislada" en ámbitos aislados. Los ámbitos aislados se utilizan a menudo en las directivas: aquí no desea que interfieran las variables de aplicación de los padres.
markmarijnissen
Si está intentando detectar ámbitos que crea pero no limpia, este método es superior. En mi caso, el rastreo del DOM siempre muestra el mismo número de ámbitos, pero los que no están unidos al DOM se multiplican en Shadowland.
SimplGy
9

Como recientemente estaba luchando con un gran número de observadores en mi aplicación, también descubrí una gran biblioteca, llamada ng-stats - https://github.com/kentcdodds/ng-stats . Tiene una configuración mínima y le brinda la cantidad de observadores en la página actual + duración del ciclo de resumen. También podría proyectar un pequeño gráfico en tiempo real.

boyomarinov
fuente
8

Mejora menor para la respuesta 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
fuente
2
Debido a que no está utilizando selectores jQuery, puede usar angular.element () en lugar de $ ()
beardedlinuxgeek
8

En AngularJS 1.3.2, countWatchersse agregó un método al módulo ngMock:

/ **
 * Método @ngdoc
 * @name $ rootScope.Scope # $ countWatchers
 * @module ngMock
 * @description
 * Cuenta todos los observadores de ámbitos secundarios directos e indirectos del alcance actual.
 * *
 * Los observadores del alcance actual están incluidos en el conteo y también lo están todos los observadores de
 * aislar los ámbitos infantiles.
 * *
 * @returns {número} Número total de observadores.
 * /

  función countWatchers () 
   {
   var root = angular.element (document) .injector (). get ('$ rootScope');
   recuento var = raíz. $$ observadores? raíz. $$ watchers.length: 0; // incluye el alcance actual
   var pendienteChildHeads = [raíz. $$ childHead];
   var currentScope;

   while (pendienteChildHeads.length) 
    {
    currentScope = PendienteChildHeads.shift ();

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

   cuenta de retorno;
   }

Referencias

Paul Sweatte
fuente
1
¡Gracias! He cambiado angular.element(document)a angular.element('[ng-app]')y lo puso en un bookmarklet con una alerta:alert('found ' + countWatchers() + ' watchers');
sin definir
4

Tomé el siguiente código directamente de la $digestfunción en sí. Por supuesto, probablemente necesite actualizar el selector de elementos de la aplicación ( document.body) en la 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'));

zorro gris
fuente
1

Estas son las funciones que 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 función utiliza un hash para no contar el mismo alcance varias veces.

erilem
fuente
Creo que a element.data()veces puede ser indefinido o algo así (al menos en una aplicación 1.0.5 en la que recibí un error cuando ejecuté este recorte e intenté llamar countWatchers). Solo para tu información.
Jared
1

Hay una función recursiva publicada por el blog de Lars Eidnes en http://larseidnes.com/2014/11/05/angularjs-the-bad-parts/ para recopilar el número total de observadores. Comparo el resultado usando la función publicada aquí y la que publicó en su blog, que ha generado un número ligeramente mayor. No puedo decir cuál es más preciso. Acabo de agregar aquí como referencia cruzada.

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: es posible que necesite cambiar "ng-app" a "data-ng-app" para su aplicación Angular.

zd333
fuente
1

La respuesta de Plantian es más rápida: https://stackoverflow.com/a/18539624/258482

Aquí hay una función que escribí a mano. No pensé en usar funciones recursivas, pero esto es lo que hice en su lugar. Puede ser más delgado, no lo sé.

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

Luego ponga esto dentro de su controlador más alto (si usa un 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');
};

Y finalmente un mercado de libros:

javascript:logScope();
Arlen Beiler
fuente
0

Un poco tarde para esta pregunta, pero yo uso esto

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

solo asegúrese de utilizar el querySelector correcto.

makrigia
fuente