مقدمة
يكمن النطاق في وضع أساسي في النظام الإيكولوجي NG. يتم تنفيذ الطبقة الأساسية للربط ثنائي الاتجاه الذي تطالب به NG للعالم الخارجي بالفعل بواسطة النطاق. يحلل هذا الفصل بشكل أساسي آلية مراقبة النطاق والميراث وتنفيذ الأحداث.
شاشة
1. ساعة $
1.1 الاستخدام
// $ watch: function (watchexp ، beasher ، Objecequality)
var unwatch = $scope.$watch('aa', function () {}, isEqual);
غالبًا ما يستخدم أولئك الذين استخدموا Angular الكود أعلاه ، والمعروف باسم "إضافة يدوي" ، ويضيف بعض المستمعين الآخرين تلقائيًا الاستماع من خلال الاستيفاء أو التوجيه ، ولكن من حيث المبدأ هم متماثلون.
1.2 تحليل رمز المصدر
الوظيفة (watchexp ، المستمع ، الكائنات) {var scope = this ، // تجميع السلاسل المحتملة في fn get = compiletofn (watchexp ، 'watch') ، array = scope. $$ watchers ، watcher = {fn: seled ، last: initwatchval ، // last value يتم تسجيلها لتسهيل المقارنة التالية ، مقارنة أو مقارنة القيمة} ؛ lastDirtyWatch = فارغة ؛ if (! isFunction (المستمع)) {var leadfn = compiletofn (المستمع || noop ، 'المستمع') ؛ Watcher.fn = function (newval ، oldval ، scope) {stisterfn (scope) ؛} ؛ } if (! array) {array = scope. $$ watchers = [] ؛ } // السبب في عدم دفع عدم الانهيار هو أنه في $ digest تبدأ حلقة المراقبين من الخلف/ من أجل تمكين مراقب المضافة حديثًا في الحلقة الحالية ، يتم وضعها في مقدمة قائمة الانتظار. unshift (مراقب) ؛ // return unwatchfn ، قم بإلغاء دالة الإرجاع الاستماع deregisterWatch () {ArrayRemove (Array ، Watcher) ؛ lastDirtyWatch = فارغة ؛ } ؛}من الكود ، ساعة $ بسيطة نسبيا. إنه يحفظ المراقب بشكل أساسي في مجموعة Watchers $$
2. $ Digest
عندما تتغير قيمة النطاق ، لن يقوم Scope بتنفيذ كل مستمع مراقب بنفسه. يجب أن يكون هناك إشعار ، والشخص الذي يرسل هذا الإشعار هو $ digest
2.1 تحليل رمز المصدر
يبلغ الكود المصدري لـ $ $ digest حوالي 100 سطر ، ويتم تركيز المنطق الرئيسي في حلقة الفحص القذرة. هناك أيضًا بعض الرموز البسيطة بعد الحلقة ، مثل معالجة ما بعدديستكويو ، لذلك لن نحلل بالتفصيل.
حلقة فحص القيمة القذرة تعني أنه طالما كان هناك تحديث لقيمة مراقب ، يجب تشغيل جولة من الاختبارات حتى لا يتم تحديث تحديثات القيمة. بالطبع ، تم إجراء بعض التحسينات لتقليل الشيكات غير الضرورية.
شفرة:
// أدخل حلقة $ digest ووضع علامة عليها لمنع الإدخال المتكرر في startPhase ('$ digest') ؛ lastDirtyWatch = null ؛ // تبدأ حلقة فحص القيمة القذرة do {dirty = false ؛ الحالي = الهدف ؛ // حلقة Asyncqueue تغفل traversescopesloop: do {if ((Watchers = current. $$ watchers)) {length = watchers.length ؛ بينما (الطول-) {try {watch = watchers [length] ؛ إذا كان (مشاهدة) {// قم بإجراء تحديث لتحديد ما إذا كانت هناك قيمة محدثة ، تتحلل على النحو التالي // value = watch.get (current) ، last = watch.last // value! == last in in in an is rected ، حدد ما إذا كان من الضروري وضع قيمة للحكم على watch.eq؟ watch.get (current))! == (last = watch.last) &&! (watch.eq؟ يساوي (القيمة ، last): (typeof value === 'number' && typeof last === 'number' && isnan (value) && isnan (last))))) {dirty = true ؛ // سجل التغييرات التي تتغير في هذه الحلقة LastDirtyWatch = Watch ؛ // Cache Last Value Watch.last = watch.eq؟ نسخة (القيمة ، فارغة): القيمة ؛ // تنفيذ المستمع (newValue ، LastValue ، Scope) // إذا تم تنفيذ التنفيذ الأول ، فسيتم تعيين LastValue أيضًا على newvalue watch.fn (القيمة ، ((آخر === initWatchVal)؟ القيمة: الأخير) ، الحالي) ؛ // ... watchlog يحذف إذا (watch.get. $$ unwatch) stableWatchesCandidates.push ({watch: watch ، array: watchers}) ؛ } // هذا هو التحسين لتقليل Watcher // إذا لم تتغير آخر ساعة تم تحديثها في الحلقة السابقة ، أي أنه لا توجد ساعة محدثة جديدة في هذه الجولة // ، فهذا يعني أن الساعات بأكملها كانت مستقرة ولن يتم تحديثها ، وتنتهي الحلقة هنا. لا تحتاج الساعات المتبقية إلى التحقق من ذلك إذا (مشاهدة === lastDirtyWatch) {dirty = false ؛ كسر traversescopesloop. }}} catch (e) {clearphase () ؛ $ استثناء Handler (e) ؛ }}} // هذا القسم متشابك بعض الشيء ، والذي يحقق فعليًا عبر العمق الأول // a-> [b-> d ، c-> e] // ender order a ، b ، d ، c ، e // احصل على الطفل الأول في كل مرة. إذا لم يكن هناك شقيق مفعم بالحيوية ، إذا لم يكن هناك أخ ، ثم ارتد إلى الطبقة السابقة وحدد ما إذا كان هناك إخوة في الطبقة. إذا لم يكن هناك ، فاستمر في التراجع حتى يتراجع إلى نطاق البداية. في هذا الوقت ، التالي == null ، لذلك سيتم الخروج من حلقة النطاقات إذا (! (التالي = (الحالي. }}} بينما ((الحالي = التالي)) ؛ // Break troverseScopesLoop يذهب مباشرة هنا // تحديد ما إذا كان لا يزال في حلقة قيمة قذرة وتجاوزت الحد الأقصى لعدد الشيكات الافتراضي TTL 10 if (((Dirty || asyncqueue.length) &&! (ttl--)) {clearphase () ؛ رمي $ rootscopeminerr ('Infdig' ، '{0} $ digest () تم الوصول إلى التكرارات. الإجهاض!/n' + 'الذين أطلقوا النار في آخر 5 تكرارات: {1}' ، ttl ، tojson (watchlog)) ؛ }} بينما (dirty || asyncqueue.length) ؛ // نهاية حلقة // مارك خروج حلقة ClearPhase () ؛هناك حلقات من 3 طبقات في الكود أعلاه
القضاة الطبقة الأولى القذرة ، إذا كانت هناك قيمة قذرة ، فاستمر في الحلقة
يفعل {
// ...
} بينما (قذر)
تحدد الطبقة الثانية ما إذا كان قد تم اجتياز النطاق. تم ترجمة الكود. على الرغم من أنه لا يزال يتم تعميمه ، إلا أنه يمكن فهمه.
يفعل {
// ....
if (الحالي. $$ childhead) {
التالي = الحالي. $$ Childhead ؛
} آخر إذا (الحالي! == target && current. $$ nextsibling) {
التالي = الحالي. $$ nextsibling ؛
}
بينما (! Next && current! == Target &&! (التالي = الحالي. $$ nextsibling)) {
الحالي = الحالي. $ parent ؛
}
} بينما (الحالي = التالي) ؛
الطبقة الثالثة من مراقبي نطاق الحلقة
الطول = المراقبين. الطول ؛
بينما (الطول-) {
يحاول {
مشاهدة = المراقبين [الطول] ؛
// ... تم حذفه
} catch (e) {
clearphase () ؛
$ استثناء Handler (e) ؛
}
}
3. $ evalasync
3.1 تحليل رمز المصدر
يتم استخدام $ evalasync للتأخر في التنفيذ ، رمز المصدر هو كما يلي:
دالة (expr) {if (! $ rootscope. $$ phase &&! $ rootscope. $$ asyncqueue.length) {$ browser.defer (function () {if ($ rootscope. } هذا. $$ asyncqueue.push ({Scope: this ، Expression: expr}) ؛}من خلال الحكم على ما إذا كان Dirty Check قيد التشغيل بالفعل ، أو قام شخص ما بتوجيه $ evalasync
if (! $ rootscope. $$ phase &&! $ rootscope. $$ asyncqueue.length) $ browser.defer هو تغيير أمر التنفيذ عن طريق استدعاء setTimeout $ browser.defer (function () {// ...}) ؛إذا كنت لا تستخدم التأجيل ، إذن
الدالة (exp) {queue.push ({scope: this ، expression: exp}) ؛ هذا. $ digest () ؛} النطاق. $ evalasync (fn1) ؛ scope. $ evalasync (fn2) ؛ // النتيجة هي // $ digest ()> fn1> $ digest ()> fn2 // ولكن يجب تحقيق التأثير الفعلي: $ digest ()> fn1> fn2تم حذف محتوى Async في القسم السابق $ Digest ، والذي يقع في الطبقة الأولى من الحلقة
بينما (asyncqueue.length) {try {asynctask = asyncqueue.shift () ؛ asynctask.scope. $ eval (asynctask.expression) ؛ } catch (e) {clearphase () ؛ $ استثناء Handler (e) ؛ } lastDirtyWatch = null ؛}بسيطة وسهلة الفهم ، تنبثق من Asynctask للتنفيذ.
ولكن هناك تفاصيل هنا ، لماذا تم إعدادها مثل هذا؟ السبب على النحو التالي. إذا تمت إضافة Asynctask جديد عند تنفيذ Watchx في حلقة معينة ، فسيتم تعيين LastDirtyWatch = Watchx في هذا الوقت. سيؤدي تنفيذ هذه المهمة إلى تنفيذ قيمة جديدة في الساعة اللاحقة لـ WatchX. إذا لم يكن هناك رمز التالي ، فستقفز الحلقة من الحلقة التالية إلى LastDirtyWatch (Watchx) ، و Dirty == False في هذا الوقت.
lastDirtyWatch = فارغة ؛
هناك أيضًا تفاصيل هنا ، لماذا حلقة في المستوى الأول؟ نظرًا لأن النطاق مع علاقة الميراث له $ $ asyncqueue ، يتم تثبيته على الجذر ، لذلك لا يلزم تنفيذها في طبقة النطاق للطبقة التالية.
2. الميراث
النطاق قابل للوراثة ، مثل $ parentcope و $ childscope اثنين. عندما يتم استدعاء $ childscope.fn إذا لم تكن هناك طريقة FN في $ childscope ، فانتقل إلى $ ParentCope للعثور على الطريقة.
ابحث عن الطبقة حسب الطبقة حتى تجد السمة المطلوبة. يتم تنفيذ هذه الميزة باستخدام خصائص ميراث النموذج الأولي لـ Javascipt.
رمز المصدر:
وظيفة (عزل) {var childscope ، الطفل ؛ إذا (عزل) {child = new scope () ؛ الطفل. $ root = هذا. $ root ؛ . الطفل. $$ postdigestqueue = هذا. $$ postdigestqueue ؛ } آخر {if (! this. $$ childscopeClass) {this. $$ childscopeClass = function () {// here يمكننا أن نرى السمات الفريدة للعزل ، مثل مراقبي $$ ، بحيث سيتم مراقبتها بشكل مستقل. هذا. $$$ مراقبين = هذا. $$ nextsibling = هذا. هذا. $$ المستمعين = {} ؛ هذا. $$ reachercount = {} ؛ هذا. $ id = nextuid () ؛ هذا. $$ childscopeClass = null ؛ } ؛ هذا. $$ childscopeclass.prototype = هذا ؛ } الطفل = جديد هذا. $$ childscopeClass () ؛ } // قم بتعيين العديد من العلاقات بين الأب والأخ ، وهو فوضوي للغاية! الطفل ['هذا'] = طفل ؛ الطفل. $ الوالدين = هذا ؛ الطفل. $$ prevesibling = هذا. $$ if (this. $$ childhead) {this. $$ inviltail. $$ nextsibling = child ؛ هذا. $$ الصفيرة = طفل ؛ } آخر {this. $$ childhead = this. $$ childtail = child ؛ } إرجاع الطفل ؛}الرمز واضح ، والتفاصيل الرئيسية هي السمات التي يجب أن تكون مستقلة وأيها تحتاج إلى أن تستند إلى الأساسيات.
أهم رمز:
this.$$childScopeClass.prototype = this;
يتحقق الميراث بهذه الطريقة.
3. آلية الحدث
3.1 $ على
الدالة (الاسم ، المستمع) {var namedlisteners = this. $$ beaders [name] ؛ if (! namedlisteners) {this. $$ leaders [name] = namedListeners = [] ؛ } namedlisteners.push (مستمع) ؛ var current = this ؛ do {if (! current. $$ readercount [name]) {current. $$ stainerCount [name] = 0 ؛ } current. $$ reachercount [name] ++ ؛ } بينما ((current = current. $ parent)) ؛ var self = this ؛ function function () {namedlisteners [indexof (nameListeners ، beasherer)] = null ؛ DEPRIMENTINGLISTENCENT (الذات ، 1 ، الاسم) ؛ } ؛}على غرار $ wathc ، يتم تخزينه أيضًا في مجموعة - NamedListeners.
هناك اختلاف آخر في أن النطاق وجميع الآباء ينقذون إحصائيات الحدث ، وهو أمر مفيد عند بث الأحداث والتحليل اللاحق.
var current = this ؛ do {if (! current. $$ stiveerCount [name]) {current. $$ readercert [name] = 0 ؛ } current. $$ reachercount [name] ++ ؛} بينما ((current = current. $ parent)) ؛3.2 $ تنبعث منها
$ emit هو حدث بث صعودي. رمز المصدر:
Function (name ، args) {var reght = [] ، namedlisteners ، scope = this ، stopPropagation = false ، event = {name: targetscope: scope ، stopPropagation: function () {stopPropagation = true ؛} ، prevationDefault: function () {event.defaultprevented = true ؛ } ، defaultPrevented: false} ، leaderargs = concat ([event] ، alcuments ، 1) ، i ، length ؛ do {nameListeners = Scope. $$ beaders [name] || فارغ؛ event.currentscope = Scope ؛ لـ (i = 0 ، length = namedlisteners.length ؛ i <length ؛ i ++) {// بعد الاستماع إلى الإزالة ، لن يتم حذفه من المصفوفة ، ولكن تم ضبطه على فارغ ، لذلك من الضروري الحكم على (! namedlisteners [i]) {namedlisteners.splice (i ، 1) ؛ أنا--؛ طول--؛ يكمل؛ } جرب {nameListeners [i] .apply (null ، leaderargs) ؛ } catch (e) {$ inscstershandler (e) ؛ }} // عند إيقاف الانتشار ، فأعود إذا (stopPropagation) {event.currentscope = null ؛ حدث العودة ؛ } // emit هي طريقة نشر النطاق الأعلى = النطاق. $ parent ؛ } بينما (النطاق) ؛ event.currentscope = null ؛ حدث العودة ؛}3.3 $ بث
البث $ ينتشر إلى الداخل ، أي ، ينتشر إلى الطفل ، رمز المصدر:
الدالة (الاسم ، args) {var target = this ، current = target ، next = target ، event = {name: name ، targetscope: target ، preventDefault: function () {event.defaultPrevented = true ؛ } ، defaultPrevented: false} ، leaderargs = concat ([event] ، editure ، 1) ، beaders ، i ، engle ؛ بينما ((current = next)) {event.currentscope = current ؛ المستمعون = الحالي. $$ المستمعين [الاسم] || [] ؛ لـ (i = 0 ، length = beaders.length ؛ i <length ؛ i ++) {// تحقق مما إذا كان قد تم إلغاء الاستماع إذا (! المستمعين [i]) {المستمعين. أنا--؛ طول--؛ يكمل؛ } جرب {المستمعين [i] .apply (null ، leaderargs) ؛ } catch (e) {$ inscstershandler (e) ؛ }} // if (next = ((current. $$ stiNederCount [name] && current. $$ childhead) || (current! == target && current. $$ nextsibling))))) }}} event.currentscope = null ؛ حدث العودة ؛}المنطق الآخر بسيط نسبيًا ، أي أن الكود الذي يتم اجتيازه في العمق أكثر إرباكًا. في الواقع ، هو نفسه كما هو الحال في Digest. هو الحكم على ما إذا كان هناك الاستماع على الطريق. الحالي. $$ الاستماع [الاسم]. من $ $ على الرمز ، يمكننا أن نرى أنه طالما كان هناك طفل على المسار والاستماع ، فإن رأس المسار لديه أيضًا رقم. على العكس من ذلك ، إذا لم يذكر أن جميع الأطفال على الطريق ليس لديهم أحداث استماع.
if (! (next = ((current. $$ stiveercount [name] && current. $$ childhead) || (current! == target && current. $$ nextsibling))))) }}
مسار الانتشار:
ROOT> [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 ؛ $ scop newNames.length ؛}) ؛ توقع ($ scope.dataCount). toequal (4) ؛ $ scope. $ digest () ؛ توقع ($ scope.dataCount) .tequal (4)
4.2 تحليل رمز المصدر
وظيفة (OBJ ، المستمع) {$ watchCollectionInterceptor. $ stateful = true ؛ var self = this ؛ var newvalue ؛ var oldvalue ؛ var fulloldvalue. var trackveryoldvalue = (leader.length> 1) ؛ var تم تغييره = 0 ؛ var changeetector = $ parse (obj ، $ watchCollectionInterceptor) ؛ var internalarray = [] ؛ var internalObject = {} ؛ var initrun = true ؛ var oldlength = 0 ؛ // تحديد ما إذا كان يجب تغيير استنادًا إلى الدالة التي تم تغييرها تم تغييرها $ watchCollectionInterceptor (_value) {// ... return agteded ؛ }.الوريد الرئيسي هو جزء من الكود المعترض أعلاه. ما يلي يحلل بشكل أساسي $ watchcollectionInterceptor و $ watchcolclection
4.3 $ WatchCollectionInterceptor
الدالة $ watchColleCtionInterceptor (_value) {newValue = _value ؛ var newlength ، key ، Bottnan ، Newitem ، OldItem ؛ إذا (isondefined (newValue)) العودة ؛ if (! isObject (newValue)) {if (oldvalue! == newValue) {oldvalue = newValue ؛ تم تغييره ++ ؛ }} آخر إذا (isArraylike (newValue)) {if (oldvalue! == internalArray) {oldvalue = internalArray ؛ oldlength = oldvalue.length = 0 ؛ تم تغييره ++ ؛ } newLength = newValue.Length ؛ if (oldlength! == newLength) {changeed ++ ؛ oldvalue.length = oldlength = newLength ؛ } لـ (var i = 0 ؛ i <newLength ؛ i ++) {OldItem = OldValue [i] ؛ newItem = newValue [i] ؛ Bottnan = (OLDITEM! == OLDITEM) && (NewItem! == NewItem) ؛ if (! botnan && (oldItem! == NewItem)) {changeed ++ ؛ oldvalue [i] = newItem ؛ }}} آخر {if (oldvalue! == internalObject) {oldvalue = internalObject = {} ؛ الطول القديم = 0 ؛ تم تغييره ++ ؛ } newLength = 0 ؛ لـ (المفتاح في newValue) {if (hasownproperty.call (newValue ، key)) {newLength ++ ؛ newItem = newValue [key] ؛ OLDITEM = OLDVALUE [KEY] ؛ if (مفتاح في oldvalue) {bottnan = (olditem! == olditem) && (newItem! == newItem) ؛ if (! botnan && (oldItem! == NewItem)) {changeed ++ ؛ oldvalue [key] = newItem ؛ }} آخر {oldlength ++ ؛ oldvalue [key] = newItem ؛ تم تغييره ++ ؛ }}} if (oldlength> newLength) {changeed ++ ؛ لـ (المفتاح في Oldvalue) {if (! hasownproperty.call (newValue ، key)) {oldlength-- ؛ حذف oldvalue [مفتاح] ؛ }}}} تم تغيير الإرجاع ؛}1). العودة مباشرة عندما تكون القيمة غير محددة.
2). عندما تكون القيمة نوعًا أساسيًا عاديًا ، حدد مباشرة ما إذا كانت متساوية.
3). عندما تكون القيمة عبارة عن صفيف فئة (أي ، توجد سمة الطول ، والقيمة [i] تسمى أيضًا صفيف الفئة) ، يتم تهيئة Oldvalue أولاً دون تهيئة
if (oldvalue! == internalArray) {oldvalue = internalArray ؛ oldlength = oldvalue.length = 0 ؛ تم تغييره ++ ؛}ثم قارن طول الصفيف ، إن لم يكن متساويًا ، يتم تسجيله على أنه تغيير تم تغييره ++
if (oldlength! == newLength) {changeed ++ ؛ oldvalue.length = oldlength = newLength ؛}قارن واحد تلو الآخر
لـ (var i = 0 ؛ i <newLength ؛ i ++) {OldItem = OldValue [i] ؛ newItem = newValue [i] ؛ Bottnan = (OLDITEM! == OLDITEM) && (NewItem! == NewItem) ؛ if (! botnan && (oldItem! == NewItem)) {changeed ++ ؛ oldvalue [i] = newItem ؛ }}4). عندما تكون القيمة كائن ، تكون عملية التهيئة مماثلة لما ورد أعلاه
if (oldvalue! == internalObject) {oldvalue = internalObject = {} ؛ الطول القديم = 0 ؛ تم تغييره ++ ؛}المعالجة التالية أكثر مهارة. إذا وجدت أن الحقول الجديدة التي تحتوي على العديد من NewValue تتم إضافة 1 ، أضف 1 إلى الطول القديم ، بحيث يضيف الطول القديم فقط ولا يطرح. من السهل العثور على ما إذا كانت هناك حقول جديدة في NewValue. أخيرًا ، قم بإزالة الحقول الإضافية في Oldvalue ، أي الحقول المحذوفة في NewValue ، ثم انتهت.
newLength = 0 ؛ for (مفتاح في newValue) {if (hasownproperty.call (newValue ، key)) {newLength ++ ؛ newItem = newValue [key] ؛ OLDITEM = OLDVALUE [KEY] ؛ if (مفتاح في oldvalue) {bottnan = (olditem! == olditem) && (newItem! == newItem) ؛ if (! botnan && (oldItem! == NewItem)) {changeed ++ ؛ oldvalue [key] = newItem ؛ }} آخر {oldlength ++ ؛ oldvalue [key] = newItem ؛ تم تغييره ++ ؛ }}} if (oldlength> newLength) {changeed ++ ؛ لـ (المفتاح في Oldvalue) {if (! hasownproperty.call (newValue ، key)) {oldlength-- ؛ حذف oldvalue [مفتاح] ؛ }}}4.4 $ WatchColcelctionAction
دالة $ watchCollectionAction () {if (initrun) {initRun = false ؛ المستمع (NewValue ، NewValue ، Self) ؛ } آخر {المستمع (newValue ، wittlevalue ، self) ؛ }. } if if (isArraylike (newValue)) {fulloldvalue = new array (newValue.Length) ؛ لـ (var i = 0 ؛ i <newvalue.length ؛ i ++) {fullOldValue [i] = newValue [i] ؛ }} else {fulloldvalue = {} ؛ لـ (var key in newValue) {if (hasownproperty.call (newValue ، key)) {fulloldvalue [key] = newValue [key] ؛ }}}}}}الكود بسيط نسبيًا ، إنه استدعاء المستمع. عندما تكون المكالمة الأولى هي OldValue == NewValue. بالنسبة للكفاءة والذاكرة ، يتم تحديد ما إذا كان المستمع يحتاج إلى معلمة OldValue.
5. $ eval & $ تطبيق
$ eval: function (expr ، alsal) {return $ parse (expr) (هذا ، السكان المحليون) ؛} ، $ application: function (expr) {try {beginphase ('$ application') ؛ إرجاع هذا. $ eval (expr) ؛ } catch (e) {$ inscstershandler (e) ؛ } أخيرًا {clearphase () ؛ حاول {$ rootscope. $ digest () ؛ } catch (e) {$ inscstershandler (e) ؛ رمي ه ؛ }}}$ application $ calls $ rootscope. $ digest () ، يوصي الكثير من الكتب باستخدام $ digest () بدلاً من الاتصال $ application () ، وهو أكثر كفاءة.
المنطق الرئيسي هو كل شيء في تحليل $ ، الذي ينتمي إلى وظيفة تحليل بناء الجملة ، وسيتم تحليله بشكل منفصل في المستقبل.