Comment compter le nombre total de montres sur une page?

144

Existe-t-il un moyen, en JavaScript, de compter le nombre de montres angulaires sur toute la page?

Nous utilisons Batarang , mais cela ne répond pas toujours à nos besoins. Notre application est grande et nous souhaitons utiliser des tests automatisés pour vérifier si le nombre de montres augmente trop.

Il serait également utile de compter les montres par contrôleur.

Edit : voici ma tentative. Il compte les montres dans tout avec la 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.
la source
Par contrôleur, c'est facile. Chacun $scopea un tableau $$ watchers avec le nombre d'observateurs sur ce contrôleur (enfin, si vous avez un ng-repeat ou quelque chose qui crée une autre portée, cela ne fonctionne pas très bien). Mais je pense qu'il n'y a aucun moyen de voir toutes les montres dans toute l'application.
Jesus Rodriguez

Réponses:

220

(Vous devrez peut-être changer bodyde lieu htmlou de lieu où vous mettez votre 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);
})();
  • Merci à erilem d'avoir signalé que cette réponse manquait la $isolateScoperecherche et que les observateurs étaient potentiellement dupliqués dans sa réponse / commentaire.

  • Merci à Ben2307 pour avoir signalé que le 'body'peut avoir besoin d'être changé.


Original

J'ai fait la même chose sauf que j'ai vérifié l'attribut data de l'élément HTML plutôt que sa classe. J'ai couru le vôtre ici:

http://fluid.ie/

Et j'ai eu 83. J'ai couru le mien et j'ai obtenu 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);
})();

J'ai aussi mis ceci dans le mien:

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

Et rien n'est imprimé, donc je suppose que le mien est meilleur (en ce sens qu'il a trouvé plus de montres) - mais je manque de connaissances angulaires intimes pour savoir avec certitude que le mien n'est pas un sous-ensemble approprié de l'ensemble de solutions.

Jared
la source
2
J'ai essayé de faire quelque chose de similaire et j'ai trouvé cet extrait très utile. Une chose qui, à mon avis, mérite d'être clarifiée, c'est que «root» doit être défini sur n'importe quel élément ayant l'attribut «ng-app», car c'est là que le $ rootScope est conservé. Dans mon application, c'est sur la balise «html». Exécution de votre script car il manque les $ watchers dans $ rootScope dans mon application.
aamiri
J'ai trouvé un problème avec le code ci-dessus: si un élément enfant a sa propre portée, mais maintenant des observateurs, $ scope. $$ watchers ne renverra-t-il pas les observateurs de la portée parent? Je pense que vous devriez ajouter avant le push quelque chose comme ça (j'utilise lodash): if (! _. Contains (watchers, watcher)) {watchers.push (watcher); }
Ben2307
2
Cela obtient tous les observateurs attachés aux éléments DOM. Si vous supprimez un élément DOM, le nettoyage des éléments associés $scopeet $$watcherautomatique est-il automatique, ou y a-t-il un impact sur les performances?
SimplGy
Tient-il compte de la nouvelle fonctionnalité de liaison unique? Votre compte ignore-t-il ce type de reliure?
systempuntoout
10
Attention: si vous l'utilisez $compileProvider.debugInfoEnabled(false);dans votre projet, cet extrait de code ne comptera aucun observateur.
Michael Klöpzig
16

Je pense que les approches mentionnées sont inexactes puisqu'elles comptent les observateurs dans le même périmètre double. Voici ma version d'un bookmarklet:

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

Il montre également quelques détails supplémentaires pour analyser les observateurs.

DTFagus
la source
12

Voici une solution hacky que j'ai mise en place basée sur l'inspection des structures de portée. Cela "semble" fonctionner. Je ne sais pas à quel point cela est précis et cela dépend certainement d'une API interne. J'utilise 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
la source
Ceci est similaire à ma solution originale (voir l'historique des modifications). J'ai adopté une approche différente parce que je pense que marcher dans la hiérarchie des portées manquerait les portées isolées.
ty.
3
Si je crée une étendue isolée avec $rootScope.$new(true)ou $scope.$new(true), où $scopeest pour le contrôleur, alors parcourir la hiérarchie trouve toujours cette étendue. Je pense que cela signifie que les prototypes ne sont pas connectés au lieu que la portée ne soit pas dans la hiérarchie.
Ian Wilson
Oui, toutes les étendues descendent de $ rootScope, seul l'héritage est "isolé" dans les étendues isolées. Les étendues isolées sont souvent utilisées dans les directives - ici, vous ne voulez pas que les variables d'application des parents interfèrent.
markmarijnissen
Si vous essayez de détecter les étendues que vous créez mais que vous ne nettoyez pas, cette méthode est supérieure. Dans mon cas, l'exploration du DOM montre toujours le même nombre de portées mais celles qui ne sont pas attachées au DOM se multiplient dans shadowland.
SimplGy
9

Comme je me débattais récemment avec un grand nombre d'observateurs dans mon application, j'ai découvert une excellente bibliothèque, appelée ng-stats - https://github.com/kentcdodds/ng-stats . Il a une configuration minimale et vous donne le nombre d'observateurs sur la page actuelle + la durée du cycle de résumé. Il pourrait également projeter un petit graphique en temps réel.

boyomarinov
la source
8

Amélioration mineure pour la réponse 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
la source
2
Parce que vous n'utilisez pas de sélecteurs jQuery, vous pouvez utiliser angular.element () au lieu de $ ()
beardedlinuxgeek
8

Dans AngularJS 1.3.2, un countWatchers méthode a été ajoutée au module ngMock:

/ **
 * Méthode @ngdoc
 * @nom $ rootScope.Scope # $ countWatchers
 * @module ngMock
 * @la description
 * Compte tous les observateurs des portées enfants directes et indirectes de la portée actuelle.
 *
 * Les observateurs de la portée actuelle sont inclus dans le décompte, de même que tous les observateurs de
 * isoler les portées enfants.
 *
 * @returns {number} Nombre total d'observateurs.
 * /

  fonction countWatchers () 
   {
   var root = angular.element (document) .injector (). get ('$ rootScope');
   var count = root. $$ observateurs? racine. $$ watchers.length: 0; // inclure la portée actuelle
   var pendingChildHeads = [racine. $$ childHead];
   var currentScope;

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

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

   nombre de retours;
   }

Références

Paul Sweatte
la source
1
Merci! J'ai changé angular.element(document)pour angular.element('[ng-app]')et le mettre dans un bookmarklet avec une alerte:alert('found ' + countWatchers() + ' watchers');
non défini
4

J'ai pris le code ci-dessous directement à partir de la $digestfonction elle-même. Bien sûr, vous devez probablement mettre à jour le sélecteur d'élément d'application ( document.body) en bas.

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

Renard gris
la source
1

Voici les fonctions que j'utilise:

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

})();

Cette fonction utilise un hachage pour ne pas compter plusieurs fois la même étendue.

Erilem
la source
Je pense que cela element.data()peut parfois être indéfini ou quelque chose du genre (au moins sur une application 1.0.5 sur laquelle j'ai eu une erreur lorsque j'ai exécuté cette copie et essayé d'appeler countWatchers). Juste FYI.
Jared
1

Il existe une fonction récursive publiée par le blog de Lars Eidnes à l'adresse http://larseidnes.com/2014/11/05/angularjs-the-bad-parts/ pour collecter le nombre total d'observateurs. Je compare le résultat en utilisant la fonction publiée ici et celle qu'il a publiée dans son blog, qui a généré un nombre légèrement plus élevé. Je ne peux pas dire lequel est le plus précis. Juste ajouté ici comme référence transversale.

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;

REMARQUE: vous devrez peut-être remplacer "ng-app" par "data-ng-app" pour votre application Angular.

zd333
la source
1

La réponse de Plantian est plus rapide: https://stackoverflow.com/a/18539624/258482

Voici une fonction que j'ai écrite à la main. Je n'ai pas pensé à utiliser des fonctions récursives, mais c'est ce que j'ai fait à la place. C'est peut-être plus maigre, je ne sais pas.

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

Ensuite, placez-le dans votre contrôleur le plus élevé (si vous utilisez un contrôleur 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');
};

Et enfin un bookmarket:

javascript:logScope();
Arlen Beiler
la source
0

Un peu tard pour cette question, mais j'utilise ceci

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

assurez-vous simplement d'utiliser le bon querySelector.

Makrigia
la source