Bagaimana cara menghitung jumlah total jam tangan pada suatu halaman?

144

Apakah ada cara, dalam JavaScript, untuk menghitung jumlah jam tangan sudut di seluruh halaman?

Kami menggunakan Batarang , tetapi itu tidak selalu sesuai dengan kebutuhan kami. Aplikasi kami besar dan kami tertarik untuk menggunakan tes otomatis untuk memeriksa apakah jumlah arloji naik terlalu banyak.

Ini juga akan berguna untuk menghitung jam tangan berdasarkan per-controller.

Sunting : inilah usaha saya. Ini menghitung jam tangan dalam segala hal dengan kelas ng-lingkup.

(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.
sumber
Per controller mudah. Setiap orang $scopememiliki array pengamat $$ dengan jumlah pengamat pada pengontrol itu (baik, jika Anda memiliki beberapa ng-repeat atau sesuatu yang menciptakan ruang lingkup lain, itu tidak berfungsi dengan baik). Tapi saya pikir tidak ada cara untuk melihat semua jam tangan di seluruh aplikasi.
Jesus Rodriguez

Jawaban:

220

(Anda mungkin perlu mengubah bodyke htmlatau ke mana pun Anda meletakkannya 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);
})();
  • Berkat erilem karena menunjukkan jawaban ini tidak ada $isolateScopepencarian dan pengamat berpotensi diduplikasi dalam jawaban / komentarnya.

  • Terima kasih kepada Ben2307 untuk menunjukkan bahwa 'body'mungkin perlu diubah.


Asli

Saya melakukan hal yang sama kecuali saya memeriksa atribut data elemen HTML daripada kelasnya. Saya menjalankan milik Anda di sini:

http://fluid.ie/

Dan mendapatkan 83. Saya menjalankan milik saya dan mendapatkan 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);
})();

Saya juga menaruh ini di tambang:

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

Dan tidak ada yang dicetak, jadi saya menduga milik saya lebih baik (karena menemukan lebih banyak arloji) - tetapi saya tidak memiliki pengetahuan sudut yang intim untuk mengetahui dengan pasti bahwa tambang bukan bagian yang tepat dari rangkaian solusi.

Jared
sumber
2
Saya telah mencoba melakukan hal serupa, dan menemukan cuplikan ini sangat membantu. Satu hal yang menurut saya layak untuk diklarifikasi adalah bahwa 'root' harus disetel ke elemen apa pun yang memiliki atribut 'ng-app', karena di situlah $ rootScope disimpan. Di aplikasi saya ada di tag 'html'. Menjalankan skrip Anda seperti melewatkan $ watchers di $ rootScope di aplikasi saya.
aamiri
Saya menemukan satu masalah dengan kode di atas: jika elemen anak memiliki cakupan sendiri, tetapi sekarang pengamat, bukankah $ lingkup. $$ pengamat mengembalikannya pengamat lingkup orangtua? Saya pikir Anda harus menambahkan sebelum mendorong sesuatu seperti ini (saya menggunakan lodash): if (! _. Berisi (watchers, watcher)) {watchers.push (watcher); }
Ben2307
2
Ini membuat semua pengamat yang melekat pada elemen DOM. Jika Anda menghapus elemen DOM, apakah pembersihan yang terkait $scopedan $$watcherotomatis, atau ada kinerja yang mereka keluarkan?
SimplGy
Apakah ini memperhitungkan fitur pengikatan satu kali yang baru? Apakah jumlah Anda melewati pengikatan semacam ini?
systempuntoout
10
Hanya kepala: Jika Anda gunakan $compileProvider.debugInfoEnabled(false);dalam proyek Anda, cuplikan ini akan menghitung nol pengamat.
Michael Klöpzig
16

Saya pikir pendekatan yang disebutkan di atas tidak akurat karena mereka menghitung pengamat dalam cakupan ganda yang sama. Ini adalah versi bookmarklet saya:

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

Ini juga menunjukkan beberapa detail untuk menganalisis pengamat.

DTFagus
sumber
12

Berikut adalah solusi hack yang saya kumpulkan berdasarkan pemeriksaan struktur ruang lingkup. "Tampaknya" berhasil. Saya tidak yakin seberapa akurat ini dan itu pasti tergantung pada beberapa API internal. Saya menggunakan 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
sumber
Ini mirip dengan solusi asli saya (lihat edit riwayat). Saya pindah ke pendekatan yang berbeda karena saya pikir berjalan hierarki ruang lingkup akan kehilangan lingkup isolasi.
ty.
3
Jika saya membuat lingkup terisolasi dengan $rootScope.$new(true)atau $scope.$new(true), di mana $scopeuntuk controller, maka berjalan hierarki masih menemukan cakupan itu. Saya pikir itu berarti bahwa prototipe tidak terhubung, bukan bahwa ruang lingkupnya tidak dalam hierarki.
Ian Wilson
Ya, semua lingkup turun dari $ rootScope, hanya warisan yang "terisolasi" dalam lingkup terisolasi. Lingkup terisolasi sering digunakan dalam arahan - di sini Anda tidak ingin variabel aplikasi dari orang tua ikut campur.
markmarijnissen
Jika Anda mencoba mendeteksi cakupan yang Anda buat tetapi tidak membersihkan, metode ini lebih unggul. Dalam kasus saya, merayapi DOM selalu menunjukkan jumlah cakupan yang sama tetapi yang tidak terikat DOM berkembang biak di bayangan.
SimplGy
9

Karena saya baru-baru ini berjuang dengan jumlah pengamat yang tinggi dalam aplikasi saya, saya juga menemukan perpustakaan yang hebat, yang disebut ng-stats - https://github.com/kentcdodds/ng-stats . Ini memiliki pengaturan minimal dan memberi Anda jumlah pengamat di halaman saat ini + panjang siklus intisari. Itu juga bisa memproyeksikan grafik kecil waktu nyata.

boyomarinov
sumber
8

Perbaikan kecil untuk jawaban 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
sumber
2
Karena Anda tidak menggunakan pemilih jQuery, Anda dapat menggunakan angular.element () alih-alih $ ()
beardedlinuxgeek
8

Di AngularJS 1.3.2, countWatchersmetode ditambahkan ke modul ngMock:

/ **
 * Metode @ngdoc
 * @name $ rootScope.Scope # $ countWatchers
 * @module ngMock
 * @deskripsi
 * Menghitung semua pengamat lingkup anak langsung dan tidak langsung dari lingkup saat ini.
 *
 * Para pengamat dari ruang lingkup saat ini termasuk dalam hitungan dan begitu juga semua pengamat
 * Mengisolasi lingkup anak.
 *
 * @returns {number} Jumlah total pengamat.
 * /

  fungsi countWatchers () 
   {
   var root = angular.element (document) .injector (). get ('$ rootScope');
   var count = root. $$ pengamat? root. $$ watchers.length: 0; // sertakan ruang lingkup saat ini
   var pendingChildHeads = [root. $$ childHead];
   var currentScope;

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

    sementara (currentScope) 
      {
      hitung + = currentScope. $$ pengamat? currentScope. $$ watchers.length: 0;
      pendingChildHeads.push (currentScope. $$ childHead);
      currentScope = currentScope. $$ nextSibling;
      }
    }

   jumlah pengembalian;
   }

Referensi

Paul Sweatte
sumber
1
Terima kasih! Aku berubah angular.element(document)ke angular.element('[ng-app]')dan memasukkannya ke dalam bookmarklet dengan peringatan:alert('found ' + countWatchers() + ' watchers');
terdefinisi
4

Saya mengambil kode di bawah ini langsung dari $digestfungsi itu sendiri. Tentu saja, Anda mungkin perlu memperbarui pemilih elemen aplikasi ( document.body) di bagian bawah.

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

Grey Fox
sumber
1

Ini adalah fungsi yang saya gunakan:

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

})();

Fungsi ini menggunakan hash untuk tidak menghitung lingkup yang sama beberapa kali.

erilem
sumber
Saya pikir element.data()kadang-kadang bisa tidak terdefinisi atau sesuatu (setidaknya pada aplikasi 1.0.5 yang saya punya kesalahan ketika saya menjalankan ini terpotong dan mencoba menelepon countWatchers). Hanya FYI.
Jared
1

Ada fungsi rekursif yang diterbitkan oleh blog Lars Eidnes di http://larseidnes.com/2014/11/05/angularjs-the-bad-parts/ untuk mengumpulkan jumlah pengamat total. Saya membandingkan hasilnya menggunakan fungsi yang diposting di sini dan yang diposting di blog-nya, yang telah menghasilkan angka yang sedikit lebih tinggi. Saya tidak tahu yang mana yang lebih akurat. Baru ditambahkan di sini sebagai referensi lintas.

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;

CATATAN: Anda mungkin perlu mengubah "ng-app" menjadi "data-ng-app" untuk aplikasi Angular Anda.

zd333
sumber
1

Jawaban Plantian lebih cepat: https://stackoverflow.com/a/18539624/258482

Berikut adalah fungsi yang saya tulis tangan. Saya tidak berpikir tentang menggunakan fungsi rekursif, tapi ini yang saya lakukan. Mungkin lebih ramping, saya tidak tahu.

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

Kemudian letakkan ini di dalam pengontrol tertinggi Anda (jika Anda menggunakan pengontrol 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');
};

Dan akhirnya sebuah pasar buku:

javascript:logScope();
Arlen Beiler
sumber
0

Agak terlambat untuk pertanyaan ini, tetapi saya menggunakan ini

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

pastikan Anda menggunakan kueriSelektor yang benar.

makrigia
sumber