Article turned from: http://www.2cto.com/kf/201408/327594.html
-
AngularJs element and model two-way binding depends on the loop to detect the value between them, this practice is called dirty detection, these days to study the source, the implementation of Angular to share. First look at how to update model changes to Ui angular model is a scope type, each scope belongs to a Directive object, such as $rootScope belong to Ng-app. from Ng-app down, each Directive created Scope will be a layer of links down, forming a $rootScope as the root of the linked list, note that Scope also has the concept of the same class, described more appropriate I think it should be a tree. We'll probably look at what members of scope have: function scope () { this. $id = Nextuid (); / /: Stage, parent scope, Watch function set, next sibling scope, previous sibling scope, first child scope, last child scope this.$ $phase = this. $paren t = this.$ $watchers = this.$ $nextSibling = this . $ $prevSibling = this.$ $childHead = this.$ $chil Dtail = null; //override this property to support prototype chain this[' this '] = this. $root =   ;this; this.$ $destroyed = false; //on current ScopeThe following asynchronous evaluation queue, which is a bunch of Angular expressions this.$ $asyncQueue = []; this.$ $postDigestQueue = [];&N Bsp this.$ $listeners = {}; this.$ $listenerCount = {}; this.$ $isolateBind ings = {};} Scope. $digest, which is the interface that Angular provides from the model update to the UI, which scope you are calling from, then it will start traversing from this scope, notifying the model to change to the individual watch functions, to see $digest Source: $ Digest:function () { var watch, value, last, watchers, Asyncqueue = this.$ $asyncQueue, Postdigestqueue = this.$ $postDigestQueue, length, dirty, ttl = ttl, Next, current, target = this, Watchlog = [], LOGIDX, logmsg, asynctask; //logo stage, prevents multiple Enter beginphase (' $digest '); //Last watch function to detect dirty values Lastdirtywatch = null;  ; &NBsp Start dirty detection, as long as there is dirty value or asynchronous queue is not empty will always loop do { dirty = false; ///current traversal to Scope&nbs P current = target; //handles All tasks in the asynchronous queue, this queue by scope. $evalAsync method input while (asyncqueue.length) { Try { Asynctask = Asyncqueue.shift (); Asynctask.scope $eval (asynctask.expression); } catch (E) { clearphase (); $exceptionHandler (e); &N Bsp } Lastdirtywatch = null; } TRAVERSESCOPESL oop: Do { //Take out all the watch functions in the current Scope if (Watche rs = current.$ $watchers) { length = watchers.length; while (length--) { Try { Watch = Watchers[len gth]; if (watch) { & nbsp 1. Take the new value of the watch function, directly compare with the last value of the Watch function //2. If the comparison fails, try calling the Watch function's Equal function, if there is no equal function then directly compare whether the old and new values are number if (value = Watch.get (c urrent)!== (last = watch.last) && ! (watch.eq equals (value, last) & nbsp : (typeof value = = ' Number ' && typeof last = = ' number ' && IsNa N (value) &&IsNaN (last))) { //detected value change, set some logo dirty = true; LASTDIRTYW Atch = watch; Watch.last = Watch.eq? Copy (value, NULL): value; //Call the change notification function of the watch function, which means that each dir Ective update from here ui Watch.fn (value, (last = = = Initwatchval) v Alue:last), current); //When Digest calls are greater than 5 (default 10) , documented for developer analysis. if (TTL < 5) { LOGIDX = 4-ttl; if (!wa TCHLOG[LOGIDX]) Watchlog[logidx] = []; logmsg = (isfunction (watch.exp)) ? ' fn: ' + (Watch.exp.name | | watch.exp.toString ()) &NB Sp : watch.exp; logmsg + = '; newval: ' + toJson (value) + '; Oldval: ' + ToJson (last); Watchlog[logidx].push (logms g); } } else if (watch = = = Lastdirtywatch) { //if the most Recen tly Dirty Watcher is now clean, short circuit since the remaining watchers /already been tested. &NBsp dirty = false; BRE AK traversescopesloop; } & nbsp } } catch (e) { clearphase () &nbs P $exceptionHandler (e); } &N Bsp } } //Forgive me, I don't understand, but what's the bottom line? &NBS P Insanity Warning:scope Depth-first traversal //Yes, this code was a bit crazy, but it works And we have tests to prove it! //This piece should being kept in sync with the traversal in $bro adcast //No child Scope, no sibling scope if (! ( Next = (current.$ $childHeAd | | (Current!== target && current.$ $nextSibling)))) { //And again I don't know why, but this time next = = = Undefined, also quit the current Scope of Watch traversal &NBSP ; while (current!== target &&!) ( Next = current.$ $nextSibling) { current = current $parent; & nbsp } } } while (current = next); //when The TTL is exhausted, there are still dirty values unhandled and the async queue throws an exception if ((Dirty | | asyncqueue.length) &&! ( ttl--) { clearphase (); throw $rootScopeMinErr (' Infdig ', &nbs P ' {0} $digest () iterations reached. aborting!\n ' + ' watchers fired in the last 5 iterations: {1} ',   ; TTL, ToJson (watchlog)); } } while (Dirty | | asyNcqueue.length); //Exit digest stage, allow others to call Clearphase (); while ( Postdigestqueue.length) { Try { Postdigestqueue.shift () (); &N Bsp } catch (E) { $exceptionHandler (e); } } } Although it looks long, it's easy to understand , the default is to start from the $rootScope, to evaluate each watch function, the new value is called the notification function, the notification function updates the UI, we look at Ng-model is how to register the: $scope of the notification function. $watch (function Ngmodelwatch () { var value = Ngmodelget ($scope); //Ng-model if Modelvalue current record is not equal to scope The latest value if (ctrl. $modelValue!== value) { var formatters = Ctrl. $formatters, &N Bsp IDX = formatters.length; //Use formatter format new values, such as Number,email &NBS P Ctrl. $modelValue = value; while (idx--) { value = Formatters[idx] (value); &nb Sp }&NBSP;&NBSp //update new values to ui if (ctrl. $viewValue!== value) { CTRL. $viewValue = value; CTRL. $render (); } } return value; }); How does UI change update to Model very simple, by Directive compile-time binding event, such as Ng-model bound to an input box when the event code is as follows: var ngeventdirectives = {};for Each ( ' click DblClick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress su Bmit focus Blur copy cut Paste '. Split ('), function (name) { var directivename = directivenormalize (' ng-' + name); Ngeventdirectives[directivename] = [' $parse ', function ($parse) { return { compile:function ($element, attr) { &NBS P;VAR fn = $parse (Attr[directivename]); return function (scope, element, attr) {//trigger above specified event, the scope and event objects of the element are sent to direcive element.on (lowercase (name), function (even T) { scope $apply (function () { &NB Sp &NBSP;FN (Scope, {$event: event}); }); &NBS p;}); }; } }; }];&NBSP ; });D irective receive the input event and then Update the Model as needed. believe that after the above research should be on the Angular binding mechanism quite understand it, now do not talk to others about the dirty detection is a while (true) has been in the evaluation of the efficiency of a good low what, and you usually use the event no different, a few more cycles. Finally note that you usually do not need to call scope manually. $digest, especially when your code is being called back in a $digest because it has entered the digest phase so you can throw an exception if you call it again. We're only in no Scop.The code inside the e context needs to call Digest, because at this point you are Angular unaware of the UI or Model changes.
Angularjs bidirectional binding mechanism parsing