Perkenalan
Lingkup terletak pada posisi inti di ekosistem NG. Lapisan yang mendasari ikatan dua arah yang diklaim oleh NG ke dunia luar sebenarnya diterapkan oleh ruang lingkup. Bab ini terutama menganalisis mekanisme arloji, warisan, dan implementasi acara.
monitor
1. $ Watch
1.1 Penggunaan
// $ Watch: Fungsi (WatchExp, pendengar, ObjectEquality)
var unwatch = $scope.$watch('aa', function () {}, isEqual);
Mereka yang telah menggunakan Angular akan sering menggunakan kode di atas, umumnya dikenal sebagai "secara manual" menambahkan mendengarkan, dan beberapa pendengar lain secara otomatis menambahkan mendengarkan melalui interpolasi atau arahan, tetapi pada prinsipnya mereka sama.
1.2 Analisis Kode Sumber
function(watchExp, listener, objectEquality) { var scope = this, // Compile possible strings into fn get = compileToFn(watchExp, 'watch'), array = scope.$$watchers, watcher = { fn: listener, last: initWatchVal, // Last value is recorded to facilitate the next comparison get: get, exp: watchExp, eq: !!objectEquality // Is the configuration reference perbandingan atau nilai perbandingan}; lastDirtyWatch = null; if (! isFunction (pendengar)) {var listenfn = compileToFn (pendengar || noop, 'listener'); watcher.fn = function (newVal, oldval, scope) {listenfn (scope);}; } if (! array) {array = scope. $$ watchers = []; } // Alasan mengapa Unshift tidak didorong adalah karena di $ Digest, Loop Watchers dimulai dari belakang/ untuk memungkinkan Watcher yang baru ditambahkan dieksekusi dalam loop saat ini, ditempatkan di bagian depan antrian.unshift (Watcher); // kembalikan unwatchfn, batal mendengarkan fungsi pengembalian deregisterwatch () {arrayremove (array, watcher); lastDirtyWatch = null; };}Dari kode, $ watch relatif sederhana. Ini terutama menyelamatkan pengamat ke dalam array $$ Watchers
2. $ Digest
Ketika nilai lingkup berubah, ruang lingkup tidak akan menjalankan setiap pengamat dengan sendirinya. Harus ada pemberitahuan, dan orang yang mengirimkan pemberitahuan ini adalah $ Digest
2.1 Analisis Kode Sumber
Kode sumber seluruh $ digest adalah sekitar 100 baris, dan logika utama terkonsentrasi dalam loop cek kotor. Ada juga beberapa kode kecil setelah loop, seperti pemrosesan postdigestqueue, jadi kami tidak akan menganalisis secara rinci.
Loop periksa nilai kotor berarti bahwa selama ada pembaruan untuk nilai pengamat, putaran cek harus dijalankan sampai tidak ada pembaruan nilai yang diperbarui. Tentu saja, beberapa optimisasi telah dibuat untuk mengurangi cek yang tidak perlu.
Kode:
// Masukkan loop $ Digest dan tandai untuk mencegah entri berulang ke BeginPhase ('$ digest'); lastDirtyWatch = null; // Loop Nilai Kotor Loop Mulai Do {Dirty = False; saat ini = target; // loop asyncqueue menghilangkan traversescopesloop: do {if ((pengamat = saat ini. $$ Watchers)) {length = watchers.length; while (length--) {coba {watch = Watchers [length]; if (watch) { // Make an update to determine whether there is a value updated, decompose as follows // value = watch.get(current), last = watch.last // value !== last If it is true, then determine whether it is necessary to make a value to judge watch.eq?equals(value, last) // If it is not a judgment of equal values, determine the situation of NaN, that is, NaN !== NaN if ((value = watch.get (saat ini))! == (last = watch.Last) &&! (watch.eq? sama (nilai, terakhir): (typeof value === 'nomor' && typeof terakhir === 'nomor' && isnan (nilai) && isnan (terakhir)))) {Dirty = true true; // Rekam perubahan tontonan mana dalam loop ini lastDirtyWatch = tonton; // Cache Nilai Terakhir Watch.Last = Watch.eq? salin (nilai, null): nilai; // Jalankan ListenerFN (NewValue, LastValue, Scope) // Jika eksekusi pertama dilakukan, maka LastValue juga diatur ke newValue watch.fn (nilai, ((terakhir === initwatchval)? Nilai: terakhir), saat ini); // ... WatchLog menghilangkan jika (watch.get. $$ AWAB) stabilwatchescandides.push ({watch: watch, array: watchers}); } // Ini adalah optimasi untuk mengurangi Watcher // Jika arloji terakhir yang diperbarui di loop sebelumnya tidak berubah, yaitu, tidak ada jam tangan baru yang diperbarui di babak ini // maka itu berarti bahwa seluruh jam tangan telah stabil dan tidak akan diperbarui, dan loop berakhir di sini. Jam tangan yang tersisa tidak perlu diperiksa lagi jika (watch === lastDirtyWatch) {Dirty = false; Break Traversescopesloop; }}} catch (e) {clearphase (); $ ExceptionHandler (E); }}} // Bagian ini sedikit kusut, yang sebenarnya menyadari kedalaman-first traversal // a-> [b-> d, c-> e] // Eksekusi urutan A, B, D, C, E // Dapatkan anak pertama setiap kali. Jika tidak ada saudara berikutnya, jika tidak ada saudara, maka mundur ke lapisan sebelumnya dan tentukan apakah ada saudara di lapisan. Jika tidak ada, terus mundur sampai mundur ke ruang lingkup awal. Pada saat ini, NEXT == NULL, jadi loop lingkup akan keluar jika (! (NEXT = (saat ini. $$ childhead || (saat ini! == target && saat ini. $$ nextSibling)))) {while (saat ini! == target &&! (NEXT = Current. $$ nextSibling)))) {saat ini = saat ini. $ Parent; }}} while ((current = next)); // break traversescopesloop pergi langsung di sini // Tentukan apakah masih dalam loop nilai kotor dan telah melebihi jumlah maksimum cek TTL default 10 if ((kotor || asyncqueue.length) &&! (ttl--)) {clearphase (); Lempar $ rootscopeminerr ('infdig', '{0} $ digest () iterasi tercapai. ABORTING!/N' + 'Watchers menembakkan 5 iterasi terakhir: {1}', ttl, tojson (watchlog)); }} while (kotor || asyncqueue.length); // akhir loop // Mark Exit Digest loop clearphase ();Ada 3 lapis loop dalam kode di atas
Hakim Lapisan Pertama Kotor, jika ada nilai kotor, lalu lanjutkan untuk mengulang
Mengerjakan {
// ...
} while (kotor)
Lapisan kedua menentukan apakah ruang lingkup telah dilalui. Kode telah diterjemahkan. Meskipun masih diedarkan, dapat dipahami.
Mengerjakan {
// ....
if (saat ini. $$ childhead) {
Berikutnya = Saat Ini. $$ Childhead;
} lain jika (saat ini! == target && saat ini. $$ nextSibling) {
Next = saat ini. $$ NextSibling;
}
while (! next && current! == target &&! (next = current. $$ nextSibling)) {
Saat ini = saat ini. $ Parent;
}
} while (current = next);
Lapisan ketiga pengamat lingkup loop
Length = Watchers.length;
while (length--) {
mencoba {
tonton = pengamat [panjang];
// ... dihilangkan
} catch (e) {
clearphase ();
$ ExceptionHandler (E);
}
}
3. $ Evalasync
3.1 Analisis Kode Sumber
$ evalasync digunakan untuk eksekusi tertunda, kode sumber adalah sebagai berikut:
function (expr) {if (! $ rootscope. $$ fase &&! $ rootscope. $$ asyncqueue.length) {$ browser.defer (function () {if ($ rootscope. $$ asyncqueue.length) {$ rootscope. $ digest ();}); } ini. $$ asyncqueue.push ({scope: this, ekspresi: expr});}Dengan menilai apakah cek kotor sudah berjalan, atau seseorang telah memicu $ evalasync
if (! $ rootscope. $$ fase &&! $ rootscope. $$ asyncqueue.length) $ browser.defer adalah untuk mengubah pesanan eksekusi dengan menelepon setTimeout $ browser.defer (function () {// ...});Jika Anda tidak menggunakan tundukan, maka
function (exp) {queue.push ({scope: this, ekspresi: exp}); ini. $ digest ();} lingkup. $ evalasync (fn1); cakupan. $ evalasync (fn2); // hasilnya adalah // $ digest ()> fn1> $ digest ()> fn2 // tetapi efek sebenarnya perlu dicapai: $ digest ()> fn1> fn2Isi async dihilangkan di bagian sebelumnya $ digest, yang terletak di lapisan pertama loop
while (asyncqueue.length) {coba {asynctask = asyncqueue.shift (); asynctask.scope. $ eval (asynctask.expression); } catch (e) {clearphase (); $ ExceptionHandler (E); } lastDirtyWatch = null;}Sederhana dan mudah dimengerti, pop up asynctask untuk dieksekusi.
Tapi ada detail di sini, mengapa diatur seperti ini? Alasannya adalah sebagai berikut. Jika asynctask baru ditambahkan ketika WatchX dieksekusi dalam loop tertentu, LastDirtyWatch = WatchX akan ditetapkan saat ini. Eksekusi tugas ini akan menyebabkan nilai baru dieksekusi di WatchX selanjutnya. Jika tidak ada kode berikut, maka loop akan melompat keluar dari loop berikutnya ke LastDirtyWatch (WatchX), dan Dirty == false saat ini.
lastDirtyWatch = null;
Ada juga detail di sini, mengapa loop di tingkat pertama? Karena ruang lingkup dengan hubungan warisan memiliki $$ asyncqueue, semuanya dipasang pada akar, sehingga tidak perlu dieksekusi di lapisan lingkup lapisan berikutnya.
2. Warisan
Lingkupnya diwariskan, seperti $ Parentscope dan $ Childscope dua lingkup. Ketika $ childscope.fn dipanggil jika tidak ada metode FN dalam $ childscope, lalu pergi ke $ parentscope untuk menemukan metode.
Cari lapisan demi lapisan sampai Anda menemukan atribut yang diperlukan. Fitur ini diimplementasikan menggunakan karakteristik pewarisan prototipe Javascipt.
Kode Sumber:
function (isolate) {var childscope, child; if (isolate) {child = new scope (); anak. $ root = ini. $ root; // Asyncqueue dan postdigestqueue dari isolat juga merupakan akar publik, dan anak -anak independen lainnya. $$ asyncqueue = this. $$ asyncqueue; anak. $$ postdigestqueue = ini. $$ Postdigestqueue; } else {if (! this. $$ childscopeClass) {this. $$ childscopeClass = function () {// Di sini kita dapat melihat atribut mana yang unik untuk mengisolasi, seperti pengamat $$, sehingga mereka akan dipantau secara independen. Ini. $$$ Watchers = this. $$ nextSibling = this. $$ childhead = this. $$ childtail = null; ini. $$ listeners = {}; ini. $$ listenerCount = {}; ini. $ id = nextUid (); ini. $$ childscopeClass = null; }; ini. $$ childscopeClass.prototype = this; } anak = baru ini. $$ childscopeClass (); } // Tetapkan berbagai hubungan ayah-anak dan saudara laki-laki, yang sangat berantakan! anak ['ini'] = anak; anak. $ Parent = ini; anak. $$ Fakularbah = ini. $$ Childtail; if (this. $$ childhead) {this. $$ childtail. $$ nextSibling = child; ini. $$ childtail = anak; } else {this. $$ childhead = this. $$ childtail = anak; } return child;}Kode ini jelas, detail utama adalah atribut mana yang harus mandiri dan mana yang perlu didasarkan pada dasar -dasarnya.
Kode terpenting:
this.$$childScopeClass.prototype = this;
Warisan direalisasikan dengan cara ini.
3. Mekanisme acara
3.1 $ aktif
function (name, listener) {var namedListeners = this. $$ pendengar [nama]; if (! NamedListeners) {this. $$ pendengar [name] = namedListeners = []; } namedListeners.push (pendengar); var arus = ini; do {if (! Current. $$ listenerCount [name]) {saat ini. $$ listenerCount [name] = 0; } saat ini. $$ listenerCount [name] ++; } while ((current = current. $ parent)); var self = ini; return function () {namedListeners [indexOf (namedListeners, listener)] = null; decrementlistenercount (self, 1, name); };}Mirip dengan $ wathc, ia juga disimpan dalam array - bernama NamedListeners.
Ada perbedaan lain bahwa ruang lingkup dan semua orang tua menyimpan statistik peristiwa, yang berguna saat menyiarkan acara dan analisis selanjutnya.
var current = this; do {if (! current. $$ listenerCount [name]) {current. $$ listenerCount [name] = 0; } saat ini. $$ listenerCount [name] ++;} while ((saat ini = saat ini. $ parent));3.2 $ emit
$ emit adalah acara siaran ke atas. Kode Sumber:
function (name, args) {var kosong = [], namedListeners, scope = this, stopPropagation = false, event = {name: name, targetscope: scope, stoppropagation: function () {stoppropagation = true;}, preventDefault: function () {event.defaultPrevent = true; }, defaultPrevented: false}, listenerArgs = concat ([event], argumen, 1), i, panjang; do {namedListeners = scope. $$ pendengar [nama] || kosong; event.currentscope = scope; untuk (i = 0, panjang = namedListeners.length; i <length; i ++) {// Setelah mendengarkan untuk menghapus, itu tidak akan dihapus dari array, tetapi diatur ke nol, jadi perlu untuk menilai jika (! NamedListeners [i]) {namedListeners.splice (i, 1); Saya--; panjang--; melanjutkan; } coba {namedListeners [i] .Apply (null, listenerArgs); } catch (e) {$ exceptionHandler (e); }} // Saat propagasi dihentikan, kembalikan jika (stopPropagation) {event.currentscope = null; acara kembali; } // memancarkan cara untuk merambat ke atas. SCOPE = SCOPE. $ Parent; } while (scope); event.currentscope = null; Kembalikan acara;}3,3 $ siaran
$ siaran menyebar ke dalam, yaitu, merambat ke anak, kode sumber:
function (name, args) {var target = this, current = target, next = target, event = {name: name, targetscope: target, preventDefault: function () {event.defaultPrevented = true; }, defaultPrevented: false}, listenerArgs = concat ([event], argumen, 1), pendengar, i, panjang; while ((current = next)) {event.currentscope = current; pendengar = saat ini. $$ pendengar [nama] || []; untuk (i = 0, length = listeners.length; i <length; i ++) {// Periksa apakah mendengarkan telah dibatalkan jika (! pendengar [i]) {listeners.splice (i, 1); Saya--; panjang--; melanjutkan; } coba {pendengar [i] .Apply (null, listenerArgs); } catch (e) {$ exceptionHandler (e); }} // if (next = ((saat ini. $$ listenerCount [nama] && saat ini. $$ childhead) || (saat ini! == target && saat ini. $$ nextSibling)))) {while (saat ini! == target &&! (Next = saat ini. $$ nextSibling)) {Current = Current. }}} event.currentscope = null; Kembalikan acara;}Logika lainnya relatif sederhana, yaitu kode yang dilintasi secara mendalam lebih membingungkan. Bahkan, itu sama seperti di Digest. Ini untuk menilai apakah ada mendengarkan di jalan setapak. Saat ini. $$ listenerCount [nama]. Dari kode di atas, kita dapat melihat bahwa selama ada anak di jalan dan mendengarkan, header Path juga memiliki nomor. Sebaliknya, jika tidak dinyatakan bahwa semua anak di jalan tidak memiliki peristiwa mendengarkan.
if (! (next = ((saat ini. $$ listenerCount [name] && current. $$ childhead) || (saat ini! == target && saat ini. $$ nextSibling)))) {while (saat ini! == target &&! (NEXT = Current. $$ NextSibling)) {saat ini = saat ini. $ Parent; $ Parent; }}Jalur propagasi:
Root> [a> [a1, a2], b> [b1, b2> [c1, c2], b3]]
Root> A> A1> A2> B> B1> B2> C1> C2> B3
4. $ WatchCollection
4.1 Gunakan contoh
$ scope.names = ['igor', 'matias', 'misko', 'james']; $ scope.datacount = 4; $ scope. $ watchcollection ('names', function (newnames, oldnames) {$ scope.datacount = newnames.length;}); harapkan ($ scope.datacount) .toequal (4); $ scope. $ digest (); harapkan ($ scope.datacount) .toequal (4); $ scope.names.pop (); $ scope. $ digest (); harapkan ($ scope.datacount).4.2 Analisis Kode Sumber
fungsi (obj, pendengar) {$ watchCollectionInterceptor. $ stateful = true; var self = ini; var newValue; var oldvalue; var veryloldValue; var trackVeryLoValue = (listener.length> 1); var diubah dikeluarkan = 0; var ganti ganti = $ parse (obj, $ watchCollectionInterceptor); var internalArray = []; var internalObject = {}; var initrun = true; var oldlength = 0; // Tentukan apakah akan berubah berdasarkan fungsi yang dikembalikan yang dikembalikan $ watchCollectionInceptor (_Value) {// ... return diubah yang diubah; } // Hubungi pendengar asli melalui metode ini sebagai fungsi proxy $ watchCollectionAction () {} kembalikan ini. $ Watch (ChangeDetector, $ watchCollectionAction);}Vena utama adalah bagian dari kode yang dicegat di atas. Berikut ini terutama menganalisis $ WatchCollectionInterceptor dan $ WatchCollectionAction
4.3 $ WatchCollectionInterceptor
fungsi $ watchCollectionInceptor (_value) {newValue = _value; var newlength, key, bothnan, newitem, olditem; if (ISundefined (newValue)) kembali; if (! isObject (newValue)) {if (oldvalue! == newValue) {oldValue = newValue; diubah dikelilingi ++; }} lain if (isArray like (newValue)) {if (oldvalue! == internalArray) {oldValue = internalArray; Oldlength = oldValue.length = 0; diubah dikelilingi ++; } newLength = newValue.length; if (oldlength! == newLength) {ganti yang diubah ++; oldValue.length = oldlength = newLength; } untuk (var i = 0; i <newlength; i ++) {olditem = oldvalue [i]; newItem = newValue [i]; Bothnan = (oldItem! == oldItem) && (newItem! == newItem); if (! Bothnan && (oldItem! == newItem)) {ganti yang diubah ++; OldValue [i] = newItem; }}} else {if (oldvalue! == internalObject) {oldvalue = internalObject = {}; Oldlength = 0; diubah dikelilingi ++; } newLength = 0; untuk (kunci di newValue) {if (hasownproperty.call (newValue, key)) {newLength ++; newItem = newValue [key]; Olditem = OldValue [Key]; if (kunci di oldvalue) {bothnan = (olditem! == olditem) && (newItem! == newItem); if (! Bothnan && (oldItem! == newItem)) {ganti yang diubah ++; OldValue [Key] = NewItem; }} else {oldlength ++; OldValue [Key] = NewItem; diubah dikelilingi ++; }}} if (oldlength> newLength) {changedetected ++; untuk (kunci di oldvalue) {if (! hasownproperty.call (newValue, key)) {oldlength--; Hapus OldValue [Key]; }}}} return diubah yang diubah;}1). Kembali secara langsung saat nilainya tidak terdefinisi.
2). Ketika nilainya adalah tipe dasar biasa, secara langsung menentukan apakah itu sama.
3). Ketika nilainya adalah array kelas (yaitu, atribut panjang ada, dan nilai [i] juga disebut array kelas), value old diinisialisasi terlebih dahulu tanpa inisialisasi
if (oldValue! == internalArray) {oldValue = internalArray; Oldlength = oldValue.length = 0; diubah dikeluarkan ++;}Kemudian bandingkan panjang array, jika tidak sama, direkam sebagai perubahan yang diubah diubah ++
if (oldlength! == newLength) {ganti yang diubah ++; oldValue.length = oldlength = newLength;}Bandingkan satu per satu
untuk (var i = 0; i <newlength; i ++) {olditem = oldvalue [i]; newItem = newValue [i]; Bothnan = (oldItem! == oldItem) && (newItem! == newItem); if (! Bothnan && (oldItem! == newItem)) {ganti yang diubah ++; OldValue [i] = newItem; }}4). Saat nilainya objek, proses inisialisasi mirip dengan yang di atas
if (oldvalue! == internalObject) {oldValue = internalObject = {}; Oldlength = 0; diubah dikeluarkan ++;}Pemrosesan berikutnya lebih terampil. Jika Anda menemukan bahwa bidang baru dengan banyak nilai baru ditambahkan, tambahkan 1 ke Oldlength, sehingga OldLength hanya menambah dan tidak mengurangi. Sangat mudah untuk menemukan apakah ada bidang baru di NewValue. Akhirnya, lepaskan bidang tambahan di OldValue, yaitu bidang yang dihapus di NewValue, dan kemudian sudah berakhir.
newLength = 0; for (tombol di newValue) {if (hasownproperty.call (newValue, key)) {newLength ++; newItem = newValue [key]; Olditem = OldValue [Key]; if (kunci di oldvalue) {bothnan = (olditem! == olditem) && (newItem! == newItem); if (! Bothnan && (oldItem! == newItem)) {ganti yang diubah ++; OldValue [Key] = NewItem; }} else {oldlength ++; OldValue [Key] = NewItem; diubah dikelilingi ++; }}} if (oldlength> newLength) {changedetected ++; untuk (kunci di oldvalue) {if (! hasownproperty.call (newValue, key)) {oldlength--; Hapus OldValue [Key]; }}}4.4 $ WatchCollectionAction
fungsi $ watchCollectionAction () {if (initrun) {initrun = false; pendengar (newValue, newValue, self); } else {listener (newValue, veryloldValue, self); } // trackVeryLoLValue = (listener.length> 1) Periksa apakah metode pendengar memerlukan oldvalue // salin jika perlu jika (trackVeryLoLValue) {if (! isObject (newValue)) {verdoldValue = newValue; } lain if (isArray like (newValue)) {veryloldValue = new array (newValue.length); untuk (var i = 0; i <newValue.length; i ++) {veryloLvalue [i] = newValue [i]; }} else {veryloldValue = {}; untuk (tombol var di newValue) {if (hasownproperty.call (newValue, key)) {verylOlvalue [key] = newValue [key]; }}}}}}Kode ini relatif sederhana, itu untuk menghubungi ListenerFN. Ketika panggilan pertama adalah OldValue == NewValue. Untuk efisiensi dan memori, ditentukan apakah pendengar membutuhkan parameter OldValue.
5. $ eval & $ berlaku
$ eval: function (expr, locals) {return $ parse (expr) (this, penduduk setempat);}, $ apply: function (expr) {try {beginphase ('$ apply'); kembalikan ini. $ eval (expr); } catch (e) {$ exceptionHandler (e); } akhirnya {clearphase (); coba {$ rootscope. $ digest (); } catch (e) {$ exceptionHandler (e); lempar e; }}}$ apply akhirnya memanggil $ rootscope. $ digest (), begitu banyak buku merekomendasikan menggunakan $ digest () alih -alih menelepon $ apply (), yang lebih efisien.
Logika utama semuanya dalam $ parse, yang termasuk dalam fungsi parsing sintaks, dan akan dianalisis secara terpisah di masa depan.