การแนะนำ
ขอบเขตอยู่ในตำแหน่งหลักในระบบนิเวศของ NG ชั้นพื้นฐานของการผูกมัดสองทางที่อ้างสิทธิ์โดย NG ไปยังโลกภายนอกนั้นถูกนำมาใช้จริงโดยขอบเขต บทนี้ส่วนใหญ่วิเคราะห์กลไกการเฝ้าดูของขอบเขตการถ่ายทอดและการใช้งานเหตุการณ์
เฝ้าสังเกต
1. $ ดู
1.1 ใช้
// $ watch: function (watchexp, ผู้ฟัง, Objectequality)
var unwatch = $scope.$watch('aa', function () {}, isEqual);
ผู้ที่ใช้ Angular มักจะใช้รหัสด้านบนซึ่งเป็นที่รู้จักกันทั่วไปว่า "การเพิ่มการฟังด้วยตนเองและผู้ฟังคนอื่น ๆ เพิ่มการฟังผ่านการแก้ไขหรือคำสั่งโดยอัตโนมัติ แต่ในหลักการพวกเขาเหมือนกัน
1.2 การวิเคราะห์รหัสซอร์ส
ฟังก์ชั่น (watchExp, ผู้ฟัง, objectequality) {var scope = this, // รวบรวมสตริงที่เป็นไปได้เป็น fn get = compileTofn (watchExp, 'watch'), array = ขอบเขต $$ watcher, watcher = {fn: ผู้ฟัง, ล่าสุด: initwatchval, // ค่าสุดท้าย การเปรียบเทียบหรือการเปรียบเทียบค่า}; LastDhirtyWatch = NULL; if (! isfunction (ผู้ฟัง)) {var listenfn = compileTofn (ผู้ฟัง || noop, 'Listener'); watcher.fn = ฟังก์ชั่น (newVal, oldVal, ขอบเขต) {Listenfn (ขอบเขต);}; } if (! array) {array = scope. $$ watchers = []; } // เหตุผลที่ไม่ได้ผลักดันไม่ได้เป็นเพราะใน $ Digest ลูปผู้เฝ้าดูเริ่มต้นจากด้านหลัง/ เพื่อเปิดใช้งานผู้เฝ้าดูที่เพิ่มขึ้นใหม่ที่จะดำเนินการในลูปปัจจุบันมันถูกวางไว้ที่ด้านหน้าของคิว unshift (ดู); // return unwatchfn, ยกเลิกฟังก์ชั่นการฟังการฟัง DeregisterWatch () {ArrayRemove (Array, Watcher); LastDhirtyWatch = NULL; -จากรหัส $ นาฬิกาค่อนข้างง่าย ส่วนใหญ่ช่วยให้ผู้เฝ้าดูเป็นอาร์เรย์ $$ Watchers
2. $ Digest
เมื่อค่าขอบเขตเปลี่ยนแปลงขอบเขตจะไม่เรียกใช้งานผู้ดูแต่ละคนด้วยตัวเอง จะต้องมีการแจ้งเตือนและผู้ที่ส่งการแจ้งเตือนนี้คือ $ digest
2.1 การวิเคราะห์รหัสที่มา
ซอร์สโค้ดของ $ digest ทั้งหมดประมาณ 100 บรรทัดและตรรกะหลักจะเข้มข้นในลูปตรวจสอบสกปรก นอกจากนี้ยังมีรหัสเล็กน้อยหลังจากลูปเช่นการประมวลผลของ postdigestqueue ดังนั้นเราจะไม่วิเคราะห์รายละเอียด
ลูปตรวจสอบค่าสกปรกหมายความว่าตราบใดที่มีการอัปเดตค่าของผู้เฝ้าดูรอบของการตรวจสอบจะต้องทำงานจนกว่าจะไม่มีการอัปเดตค่า แน่นอนว่ามีการเพิ่มประสิทธิภาพบางอย่างเพื่อลดการตรวจสอบที่ไม่จำเป็น
รหัส:
// ป้อน $ digest loop และทำเครื่องหมายเพื่อป้องกันไม่ให้รายการซ้ำไปยัง engerphase ('$ digest'); lastDirtyWatch = null; // ลูปตรวจสอบค่าสกปรกเริ่มต้นทำ {สกปรก = false; current = target; // asyncqueue loop ละเว้น traversescopesloop: do {if ((watchers = current. $$ watchers)) {length = watchers.length; ในขณะที่ (ความยาว-) {ลอง {watch = watchers [ความยาว]; ถ้า (ดู) {// ทำการอัปเดตเพื่อตรวจสอบว่ามีการอัพเดทค่าหรือไม่การสลายตัวดังนี้ // value = watch.get (ปัจจุบัน), last = watch.last // value! == สุดท้ายถ้ามันเป็นจริงจากนั้นพิจารณาว่าจำเป็นหรือไม่ watch.get (current))! == (last = watch.last) &&! (watch.eq? equals (ค่า, สุดท้าย): (typeof value === 'number' && typeof last === 'number' && isnan (ค่า) && isnan (สุดท้าย)))) {dirty = true; // บันทึกที่ดูการเปลี่ยนแปลงในลูปนี้ LastDhirtyWatch = Watch; // แคชค่าสุดท้าย watch.last = watch.eq? คัดลอก (ค่า, null): ค่า; // ดำเนินการ ListenerFN (newValue, lastValue, ขอบเขต) // หากดำเนินการครั้งแรกแล้ว LastValue จะถูกตั้งค่าเป็น newValue watch.fn (ค่า ((last === initwatchval)? ค่า: สุดท้าย), ปัจจุบัน); // ... watchlog ละเว้นถ้า (watch.get. $$ UNWATCH) StableWatchEscandAtes.push ({watch: Watch, Array: Watchers}); } // นี่คือการเพิ่มประสิทธิภาพเพื่อลดผู้เฝ้าดู // หากนาฬิกาที่อัปเดตล่าสุดในลูปก่อนหน้าไม่เปลี่ยนแปลงนั่นคือไม่มีนาฬิกาปรับปรุงใหม่ในรอบนี้ // นั่นหมายความว่านาฬิกาทั้งหมดมีเสถียรภาพและจะไม่ได้รับการปรับปรุงและลูปจะสิ้นสุดที่นี่ ไม่จำเป็นต้องตรวจสอบนาฬิกาที่เหลือถ้า (ดู === lastDirtyWatch) {dirty = false; ทำลาย Traversescopesloop; }}} catch (e) {clearphase (); $ ExceptionHandler (E); }}} // ส่วนนี้ค่อนข้างพันกันซึ่งจริง ๆ แล้วตระหนักถึงความลึกในการสำรวจความลึกก่อน // a-> [b-> d, c-> e] // คำสั่งดำเนินการ A, B, D, C, E // รับลูกคนแรกในแต่ละครั้ง หากไม่มีพี่ชายของ Nextsibling หากไม่มีพี่ชายให้ถอยกลับไปที่เลเยอร์ก่อนหน้าและตรวจสอบว่ามีพี่น้องอยู่ในชั้นหรือไม่ หากไม่มีให้ดำเนินการต่อไปเรื่อย ๆ จนกว่าจะถอยกลับไปยังขอบเขตเริ่มต้น ในเวลานี้ถัดไป == null ดังนั้น scopes loop จะถูกออกถ้า (! (next = (current. $$ childhead || (ปัจจุบัน! == เป้าหมาย && current. $$ nextsibling)))) {ในขณะที่ (ปัจจุบัน! == เป้าหมาย &&! (ถัดไป = ปัจจุบัน $$ nextsibling))) }}} ในขณะที่ ((current = next)); // break traversescopesloop ไปที่นี่โดยตรง // พิจารณาว่ามันยังอยู่ในลูปค่าสกปรกและเกินจำนวนสูงสุดของการตรวจสอบ TTL เริ่มต้น 10 ถ้า ((สกปรก || asyncqueue.length) &&! (ttl--)) {clearphase (); โยน $ rootscopeminerr ('infdig', '{0} $ digest () การทำซ้ำไปแล้วการทำแท้ง!/n' + 'ผู้เฝ้าดูที่ถูกยิงในการทำซ้ำ 5 ครั้งสุดท้าย: {1}', TTL, Tojson (watchlog)); }} ในขณะที่ (สกปรก || asyncqueue.length); // จุดสิ้นสุดของลูป // มาร์คทางออก Digest Loop ClearPhase ();มีลูป 3 ชั้นในรหัสด้านบน
ชั้นแรกตัดสินสกปรกถ้ามีค่าสกปรกจากนั้นก็วนวนต่อไป
ทำ {
-
} ในขณะที่ (สกปรก)
ชั้นที่สองกำหนดว่าขอบเขตได้ถูกสำรวจหรือไม่ รหัสได้รับการแปล แม้ว่ามันจะยังคงหมุนเวียน แต่ก็สามารถเข้าใจได้
ทำ {
-
ถ้า (ปัจจุบัน. $$ childhead) {
ถัดไป = ปัจจุบัน. $$ childhead;
} อื่นถ้า (ปัจจุบัน! == Target && current. $$ nextsibling) {
ถัดไป = ปัจจุบัน. $$ nextsibling;
-
ในขณะที่ (! next && current! == target &&! (next = current. $$ nextsibling)) {
current = current. $ parent;
-
} ในขณะที่ (current = next);
ชั้นที่สามของนักดูขอบเขตลูป
ความยาว = Watchers.length;
ในขณะที่ (ความยาว-) {
พยายาม {
Watch = Watchers [ความยาว];
// ... ละเว้น
} catch (e) {
clearphase ();
$ ExceptionHandler (E);
-
-
3. $ evalasync
3.1 การวิเคราะห์รหัสที่มา
$ evalasync ใช้สำหรับการดำเนินการล่าช้าซอร์สโค้ดมีดังนี้:
ฟังก์ชั่น (expr) {ถ้า (! $ rootscope. $$ เฟส &&! $ rootscope. $$ asyncqueue.length) {$ browser.defer (ฟังก์ชั่น () {ถ้า ($ rootscope. $$ asyncqueue.length) {$ rootscope } this. $$ asyncqueue.push ({scope: this, expression: expr});}โดยการตัดสินว่าการตรวจสอบสกปรกกำลังทำงานอยู่แล้วหรือมีคนเรียก $ evalasync
if (! $ rootscope. $$ เฟส &&! $ rootscope. $$ asyncqueue.length) $ browser.defer คือการเปลี่ยนคำสั่งการดำเนินการโดยเรียก settimeout $ browser.defer (function () {// ... });หากคุณไม่ได้ใช้การเลื่อนเวลา
ฟังก์ชั่น (exp) {queue.push ({scope: this, expression: exp}); สิ่งนี้. $ digest ();} ขอบเขต. $ evalasync (fn1); ขอบเขต. $ evalasync (fn2); // ผลลัพธ์คือ // $ digest ()> fn1> $ digest ()> fn2 //เนื้อหาของ async ถูกละเว้นในส่วนก่อนหน้า $ digest ซึ่งอยู่ในชั้นแรกของลูป
ในขณะที่ (asyncqueue.length) {ลอง {asynctask = asyncqueue.shift (); asynctask.scope. $ eval (asynctask.expression); } catch (e) {clearphase (); $ ExceptionHandler (E); } lastDirtyWatch = null;}ง่ายและเข้าใจง่ายปรากฏขึ้น asynctask สำหรับการดำเนินการ
แต่มีรายละเอียดที่นี่ทำไมจึงตั้งค่าเช่นนี้? เหตุผลมีดังนี้ หากมีการเพิ่ม asynctask ใหม่เมื่อ watchx ถูกดำเนินการในลูปที่แน่นอน LastDirtyWatch = WatchX จะถูกตั้งค่าในเวลานี้ การดำเนินการของงานนี้จะทำให้ค่าใหม่ถูกดำเนินการในนาฬิกา WatchX ที่ตามมา หากไม่มีรหัสต่อไปนี้ลูปจะกระโดดออกจากลูปถัดไปไปที่ LastDhirtyWatch (WatchX) และสกปรก == FALSE ในเวลานี้
LastDhirtyWatch = NULL;
นอกจากนี้ยังมีรายละเอียดที่นี่ทำไมต้องวนอยู่ในระดับแรก? เนื่องจากขอบเขตที่มีความสัมพันธ์ในการสืบทอดมี $$ asyncqueue จึงติดตั้งอยู่บนรูททั้งหมดดังนั้นจึงไม่จำเป็นต้องดำเนินการในชั้นขอบเขตของเลเยอร์ถัดไป
2. มรดก
ขอบเขตนั้นสืบทอดได้เช่น $ Parentcope และ $ childscope สองขอบเขต เมื่อ $ childscope.fn ถูกเรียกถ้าไม่มีวิธี FN ใน $ childscope จากนั้นไปที่ $ Parentcope เพื่อค้นหาวิธีการ
ค้นหาเลเยอร์โดยเลเยอร์จนกว่าคุณจะพบแอตทริบิวต์ที่ต้องการ คุณลักษณะนี้ถูกนำไปใช้โดยใช้คุณสมบัติของการสืบทอดต้นแบบของ javascipt
ซอร์สโค้ด:
ฟังก์ชั่น (ไอโซเลต) {var childscope, เด็ก; if (isolate) {child = ขอบเขตใหม่ (); เด็ก. $ root = this. $ root; // asyncqueue และ postdigestqueue ของ Isolate ยังเป็นรากสาธารณะและเด็กอิสระอื่น ๆ $$ asyncqueue = this. $$ asyncqueue; เด็ก. $$ postdigestqueue = this. $$ postdigestqueue; } else {if (! this. $$ childscopeclass) {this. $$ childscopeclass = function () {// ที่นี่เราสามารถดูได้ว่าคุณลักษณะใดที่ไม่ซ้ำกันในการแยกเช่นผู้ดู $$ ดังนั้นพวกเขาจะได้รับการตรวจสอบอย่างอิสระ สิ่งนี้. $$$ watchers = this. $$ nextsibling = this. $$ childhead = this. $$ childtail = null; สิ่งนี้. $$ listeners = {}; สิ่งนี้. $$ ListenerCount = {}; สิ่งนี้. $ id = nextuid (); สิ่งนี้. $$ childscopeclass = null; - สิ่งนี้. $$ childscopeclass.prototype = this; } child = ใหม่สิ่งนี้ $$ childscopeclass (); } // ตั้งค่าความสัมพันธ์กับพ่อและพี่ชายหลายคนซึ่งยุ่งมาก! เด็ก ['this'] = เด็ก; เด็ก. $ parent = this; เด็ก. $$ prevsibling = this. $$ childtail; ถ้า (นี่. $$ childhead) {สิ่งนี้. $$ childtail. $$ nextsibling = เด็ก; สิ่งนี้. $$ ChildTail = เด็ก; } else {this. $$ childhead = this. $$ childTail = เด็ก; } return child;}รหัสมีความชัดเจนรายละเอียดหลักคือแอตทริบิวต์ที่ต้องมีความเป็นอิสระและสิ่งใดที่ต้องอยู่บนพื้นฐานของพื้นฐาน
รหัสที่สำคัญที่สุด:
this.$$childScopeClass.prototype = this;
การสืบทอดได้รับการรับรู้ด้วยวิธีนี้
3. กลไกเหตุการณ์
3.1 $ on
ฟังก์ชั่น (ชื่อ, ผู้ฟัง) {var nameslisteners = this. $$ listeners [ชื่อ]; if (! NamedListeners) {this. $$ listeners [name] = namedListeners = []; } NamedListeners.push (ผู้ฟัง); var current = this; ทำ {if (! current. $$ listenercount [ชื่อ]) {current. $$ ListenerCount [ชื่อ] = 0; } current. $$ LISHYERCOUNT [ชื่อ] ++; } ในขณะที่ ((current = current. $ parent)); var self = this; return function () {namedListeners [indexof (NamedListeners, Listener)] = null; DecrementListenerCount (ตัวเอง, 1, ชื่อ); -คล้ายกับ $ WATHC มันจะถูกเก็บไว้ในอาร์เรย์ - NamedListeners
มีความแตกต่างอีกประการหนึ่งที่ขอบเขตและผู้ปกครองทุกคนบันทึกสถิติเหตุการณ์ซึ่งมีประโยชน์เมื่อมีการออกอากาศเหตุการณ์และการวิเคราะห์ที่ตามมา
var current = this; do {ถ้า (! current. $$ listenercount [ชื่อ]) {current. $$ ListenerCount [ชื่อ] = 0; } current. $$ ListenerCount [ชื่อ] ++;} ในขณะที่ ((current = current. $ parent));3.2 $ emit
$ emit เป็นกิจกรรมการออกอากาศขึ้น ซอร์สโค้ด:
ฟังก์ชั่น (ชื่อ, args) {var empty = [], namedListeners, scope = this, stoppropagation = false, event = {ชื่อ: ชื่อ, targetScope: ขอบเขต, stopPropagation: function () {stopPropagation = true;}, preventDefault: function () }, defaultPrevented: false}, listenergs = concat ([เหตุการณ์], อาร์กิวเมนต์, 1), i, ความยาว; ทำ {namedListeners = ขอบเขต. $$ listeners [ชื่อ] || ว่างเปล่า; event.currentscope = ขอบเขต; สำหรับ (i = 0, length = namedListeners.length; i <length; i ++) {// หลังจากฟังลบมันจะไม่ถูกลบออกจากอาร์เรย์ แต่ถูกตั้งค่าเป็นโมฆะดังนั้นจึงจำเป็นต้องตัดสินว่า (! NamedListeners [i]) ฉัน--; ความยาว--; ดำเนินการต่อ; } ลอง {NamedListeners [i] .apply (null, Listenerargs); } catch (e) {$ exceptionhandler (e); }} // เมื่อการแพร่กระจายหยุดทำงานให้กลับมาหาก (stoppropagation) {event.currentscope = null; เหตุการณ์กลับ; } // emit เป็นวิธีการเผยแพร่ขอบเขตขึ้นไปด้านบน = ขอบเขต. $ parent; } ในขณะที่ (ขอบเขต); Event.currentscope = null; เหตุการณ์ส่งคืน;}3.3 $ ออกอากาศ
การออกอากาศ $ เป็นการแพร่กระจายภายในนั่นคือการแพร่กระจายไปยังเด็กซอร์สโค้ด:
ฟังก์ชั่น (ชื่อ, args) {var target = this, current = target, next = target, event = {ชื่อ: ชื่อ, targetScope: เป้าหมาย, preventDefault: function () {event.defaultPrevented = true; }, DefaultPrevented: False}, ListenerArgs = concat ([เหตุการณ์], อาร์กิวเมนต์, 1), ผู้ฟัง, i, ความยาว; ในขณะที่ ((current = next)) {event.currentscope = current; Listeners = ปัจจุบัน. $$ LISSENTERS [NAME] || - สำหรับ (i = 0, length = listeners.length; i <length; i ++) {// ตรวจสอบว่าการฟังถูกยกเลิกถ้า (! ผู้ฟัง [i]) {listeners.splice (i, 1); ฉัน--; ความยาว--; ดำเนินการต่อ; } ลอง {ผู้ฟัง [i] .apply (null, listenerargs); } catch (e) {$ exceptionhandler (e); }} // ถ้า (next = ((ปัจจุบัน. $$ LISYERSCOUNT [ชื่อ] && ปัจจุบัน $$ Childhead) || (ปัจจุบัน! == Target && current. $$ nextsibling)))) {ในขณะที่ (ปัจจุบัน! == เป้าหมาย &&! }}} event.currentscope = null; เหตุการณ์ส่งคืน;}ตรรกะอื่น ๆ ค่อนข้างง่ายนั่นคือรหัสที่ผ่านความลึกจะทำให้เกิดความสับสนมากขึ้น ในความเป็นจริงมันเหมือนกับใน Digest มันคือการตัดสินว่ามีการฟังบนเส้นทางหรือไม่ ปัจจุบัน. $$ ListenerCount [ชื่อ] จากรหัสด้านบนของรหัสข้างต้นเราจะเห็นได้ว่าตราบใดที่มีเด็กอยู่บนเส้นทางและฟังส่วนหัวของเส้นทางก็มีตัวเลข ในทางตรงกันข้ามหากไม่ได้ระบุว่าเด็กทุกคนบนเส้นทางไม่มีเหตุการณ์การฟัง
if (! (next = ((ปัจจุบัน. $$ listenercount [ชื่อ] && current. $$ childhead) || (ปัจจุบัน! == Target && current. $$ nextsibling)))) {ในขณะที่ (ปัจจุบัน! == เป้าหมาย &&! (ถัดไป = ปัจจุบัน -เส้นทางการแพร่กระจาย:
รูท> [a> [a1, a2], b> [b1, b2> [c1, c2], b3]]
รูท> a> a1> a2> b> b1> b2> c1> c2> b3
4. $ watchcollection
4.1 ใช้ตัวอย่าง
$ scope.names = ['igor', 'matias', 'misko', 'james']; $ scope.datacount = 4; $ scope. $ watchcollection ('ชื่อ', ฟังก์ชั่น newNames.length;}); คาดหวัง ($ scope.datacount) .toequal (4); $ scope. $ digest (); คาดหวัง ($ scope.datacount) .toequal (4); $ scope.names.pop (); $ scope. $ digest ();4.2 การวิเคราะห์รหัสที่มา
ฟังก์ชั่น (obj, ผู้ฟัง) {$ watchcollectionInterceptor. $ sateful = true; var self = this; var newValue; var oldvalue; var redoldValue; var trackVeryOldValue = (listener.length> 1); VAR เปลี่ยนการตรวจสอบ = 0; VAR CHANGETECTER = $ PARSE (OBJ, $ watchCollectionInterceptor); var internalArray = []; var internalObject = {}; var initrun = true; var oldlength = 0; // ตรวจสอบว่าจะเปลี่ยนตามฟังก์ชันที่เปลี่ยนไปที่ถูกเปลี่ยนใหม่ $ watchCollectionInterceptor (_Value) {// ... ส่งคืนการเปลี่ยนแปลงที่ถูกเปลี่ยน; } // โทรหาผู้ฟังจริงด้วยวิธีนี้เป็นฟังก์ชันพร็อกซี $ watchcollectionAction () {} ส่งคืนสิ่งนี้ $ watch (เปลี่ยนผู้ตรวจสอบ, $ watchcollectionAction);}หลอดเลือดดำหลักเป็นส่วนหนึ่งของรหัสที่สกัดด้านบน ต่อไปนี้ส่วนใหญ่วิเคราะห์ $ watchcollectionInterceptor และ $ watchcollectionAction
4.3 $ watchcollectionInterceptor
ฟังก์ชั่น $ watchCollectionInterceptor (_Value) {newValue = _Value; var newLength, key, bothnan, newItem, oldItem; ถ้า (isundefined (newValue)) กลับ; if (! isObject (newValue)) {ถ้า (oldValue! == newValue) {oldValue = newValue; เปลี่ยนเลือก ++; }} อื่นถ้า (isarraylike (newValue)) {ถ้า (oldValue! == internalArray) {oldValue = internalArray; oldLength = oldValue.length = 0; เปลี่ยนเลือก ++; } newLength = newValue.length; if (oldLength! == newLength) {เปลี่ยนเลือก ++; oldValue.length = oldLength = newLength; } สำหรับ (var i = 0; i <newLength; i ++) {oldItem = oldValue [i]; newItem = newValue [i]; BothNan = (oldItem! == oldItem) && (newItem! == newItem); if (! Bothnan && (oldItem! == newItem)) {เปลี่ยนเลือก ++; oldValue [i] = newItem; }}} else {if (oldValue! == InternalObject) {oldValue = internalObject = {}; oldLength = 0; เปลี่ยนเลือก ++; } newLength = 0; สำหรับ (คีย์ใน newValue) {ถ้า (hasownproperty.call (newValue, key)) {newLength ++; newItem = newValue [คีย์]; oldItem = oldValue [key]; if (key in oldValue) {bothnan = (oldItem! == oldItem) && (newItem! == newItem); if (! Bothnan && (oldItem! == newItem)) {เปลี่ยนเลือก ++; OldValue [key] = newItem; }} else {oldLength ++; OldValue [key] = newItem; เปลี่ยนเลือก ++; }}} if (oldLength> newLength) {เปลี่ยนเลือก ++; สำหรับ (คีย์ใน oldValue) {ถ้า (! hasownproperty.call (newValue, key)) {oldLength-; ลบ OldValue [คีย์]; }}}} return returnetected;}1). ส่งคืนโดยตรงเมื่อค่าไม่ได้กำหนด
2). เมื่อค่าเป็นประเภทพื้นฐานธรรมดาให้ตรวจสอบโดยตรงว่าเท่ากันหรือไม่
3). เมื่อค่าเป็นอาร์เรย์คลาส (นั่นคือแอตทริบิวต์ความยาวมีอยู่และค่า [i] เรียกอีกอย่างว่าอาร์เรย์คลาส) OldValue จะเริ่มต้นก่อนโดยไม่ต้องเริ่มต้น
if (oldValue! == InternalArray) {oldValue = InternalArray; oldLength = oldValue.length = 0; เปลี่ยนเลือก ++;}จากนั้นเปรียบเทียบความยาวของอาร์เรย์ถ้าไม่เท่ากันจะถูกบันทึกเป็นการเปลี่ยนแปลงการเปลี่ยนแปลงการตรวจจับ ++
if (oldLength! == newLength) {เปลี่ยนเลือก ++; oldValue.length = oldLength = newLength;}เปรียบเทียบทีละคน
สำหรับ (var i = 0; i <newLength; i ++) {oldItem = oldValue [i]; newItem = newValue [i]; BothNan = (oldItem! == oldItem) && (newItem! == newItem); if (! Bothnan && (oldItem! == newItem)) {เปลี่ยนเลือก ++; oldValue [i] = newItem; -4). เมื่อค่าเป็นวัตถุกระบวนการเริ่มต้นจะคล้ายกับข้างต้น
if (oldValue! == InternalObject) {oldValue = internalObject = {}; oldLength = 0; เปลี่ยนเลือก ++;}การประมวลผลครั้งต่อไปมีความสามารถมากกว่า หากคุณพบว่ามีการเพิ่มฟิลด์ใหม่ที่มี newValue จำนวนมากเพิ่ม 1 ถึง OldLength เพื่อให้ OldLength เพิ่มขึ้นและไม่ลบ เป็นเรื่องง่ายที่จะค้นหาว่ามีฟิลด์ใหม่ใน NewValue หรือไม่ ในที่สุดลบฟิลด์พิเศษใน OldValue นั่นคือฟิลด์ที่ถูกลบใน NewValue และจากนั้นก็จบลง
newLength = 0; สำหรับ (คีย์ใน newValue) {ถ้า (hasownProperty.call (newValue, key)) {newLength ++; newItem = newValue [คีย์]; oldItem = oldValue [key]; if (key in oldValue) {bothnan = (oldItem! == oldItem) && (newItem! == newItem); if (! Bothnan && (oldItem! == newItem)) {เปลี่ยนเลือก ++; OldValue [key] = newItem; }} else {oldLength ++; OldValue [key] = newItem; เปลี่ยนเลือก ++; }}} if (oldLength> newLength) {เปลี่ยนเลือก ++; สำหรับ (คีย์ใน oldValue) {ถ้า (! hasownproperty.call (newValue, key)) {oldLength-; ลบ OldValue [คีย์]; -4.4 $ watchcollectionAction
ฟังก์ชั่น $ watchCollectionAction () {ถ้า (initrun) {initRun = false; ผู้ฟัง (NewValue, NewValue, Self); } else {Listener (newValue, มากมาก, self); } // trackVeryOldValue = (listener.length> 1) ตรวจสอบว่าวิธีการฟังต้องการ oldValue // คัดลอกถ้าจำเป็นถ้า (trackVeryOldValue) {ถ้า (! isObject (newValue)) } else ถ้า (isarraylike (newValue)) {redoldValue = อาร์เรย์ใหม่ (newValue.length); สำหรับ (var i = 0; i <newValue.length; i ++) {มากมาก [i] = newValue [i]; }} else {มากมาก = {}; สำหรับ (คีย์ var ใน newValue) {ถ้า (hasownproperty.call (newValue, key)) {มากมาก [key] = newValue [key]; -รหัสค่อนข้างง่ายคือการโทรหา ListenerFN เมื่อการโทรครั้งแรกคือ oldValue == newValue เพื่อประสิทธิภาพและหน่วยความจำจะถูกกำหนดว่าผู้ฟังต้องการพารามิเตอร์ oldValue หรือไม่
5. $ eval & $ ใช้
$ eval: function (expr, locals) {return $ parse (expr) (นี่, locals);}, $ apple: function (expr) {ลอง {entarchase ('$ apple'); ส่งคืนสิ่งนี้ $ eval (expr); } catch (e) {$ exceptionhandler (e); } ในที่สุด {clearphase (); ลอง {$ rootscope. $ digest (); } catch (e) {$ exceptionhandler (e); โยน e; -$ apple ในที่สุดเรียก $ rootscope $ digest () หนังสือจำนวนมากแนะนำให้ใช้ $ digest () แทนการโทร $ appl () ซึ่งมีประสิทธิภาพมากขึ้น
ตรรกะหลักคือทั้งหมดใน $ parse ซึ่งเป็นของฟังก์ชันการแยกวิเคราะห์ไวยากรณ์และจะถูกวิเคราะห์แยกต่างหากในอนาคต