Share some of the things you've learned about Ng before. The first thing to say is the most important feature of NG, two-way binding. (Angular source code is all 1.5.0 version)
So what does a two-way binding code look like. Take a look at the MS Ng's online example, the code is so simple.
<script> angular.module (' bindexample ', []) . Controller(function($ Scope) { = ' whirled ';
$scope. TESTFN = function () {
Console.log ($scope. Test);
}; }]); </script><div ng-controller= "Examplecontroller" > <input type= "text" ng-model= "test "><br> <span ng-bind=" test "></span>! <span>{{test}}</span> <button class= "btn btn-default" ng-click= "Testfn ()" > Click </button ></div>
But how does ng implement two-way binding? We can see that the two-way binding is all in the $scope property. And $scope is an instance of $rootscopeprovider generation. In NG code, the scope prototype chain consists of the following methods:
$new,$digest,$watch, $watchGroup, $watchCollection,$apply, $destroy, $eval, $on, $emit, $broadcast.
Specifically, it is not introduced. There are mainly three (classes) related to data binding.
- $digest
- $watch, $watchGroup, $watchCollection
- $apply
Start with the scope constructor and look at several of its instance properties
function Scope () {this . $id = Nextuid (); this.$ $phase = this. $parent = this.$ $watchers = this.$ $nextSibling = this.$ $prevSibling = this.$ $childHead = this.$ $childTail = null; this. $root = this; this.$ $destroyed = false; this.$ $listeners = {}; this.$ $listenerCount = {}; this.$ $watchersCount = 0; this.$ $isolateBindings = null; }
Then we start with the most important $digest, $digest is the core part of the scope of the dirty check, the main function is to check the elements of the scope.wathers array, update watch.
The core code is as follows:
$digest: Function () {var watch, value, last, FN, get, Watchers,//Dirty Check object array length, TTL default dirty check loop on-line, var ttl = 10; can modify Digestttl (15); Dirty, ttl = TTL, Next, current, target = This, instance of//this scope Watchlog = [], Logidx, lo Gmsg, Asynctask; Beginphase (' $digest '); Set the status of the dirty Check//check for changes to browser URL, that happened in sync before the $digest $browser. $ $c Heckurlchange (); if (this = = $rootScope && applyasyncid!== null) {//If this is the root scope, and $applyAsync have Sch Eduled a deferred $apply (), then//cancel the scheduled $apply and flush the queue of expressions to be evaluate D. $browser. Defer.cancel (APPLYASYNCID); Flushapplyasync (); } Lastdirtywatch = null; __i = __i + 1; Console.info (' Digest: ' +__i); do {//' while dirty ' loop dirty = false; Current = TArget; while (Asyncqueue.length) {try {asynctask = Asyncqueue.shift (); Asynctask.scope. $eval (Asynctask.expression, asynctask.locals); } catch (E) {$exceptionHandler (e); } Lastdirtywatch = null; } traversescopesloop:do {//The entire dirty check starts here if ((watchers = current.$ $watchers)) { Console.log (watchers); Process our watches length = watchers.length; while (length--) {try {watch = Watchers[length]; Most common watches is on primitives, in which case we can short//circuit it with = = = operator, onl Y when = = = fails does we use. equals if (watch) {get = Watch.get; Console.log (' Value: ' +get (current)); Console.log (' Last: ' +watch.last) The current data value in value watch, stored in last watch, watch.eq whether to open the object's listener if (value = Get (current)) !== (last = watch.last) &&! (Watch.eq equals (value, last): (typeof value = = = ' Number ' &&A mp typeof last = = = ' Number ' && IsNaN (value) && IsNaN (last))) { Dirty = true; Lastdirtywatch = watch; $digest the last watch triggered. Why is it? See below watch.last = Watch.eq? Copy (value, NULL): value; fn = Watch.fn; fn (value, (last = = = Initwatchval), value:last), current); Invoke the Listener function if (TTL < 5) {logidx = 4-ttl; if (!watchlog[logidx]) watchlog[logidx] = []; Watchlog[logidx].push ({msg:isfunction (watch.exp)? ' fn: ' + (watch.exp. name | | Watch.exp.toString ()): Watch.exp, Newval:value, Oldval:last }); }} else if (watch = = = Lastdirtywatch) {//Because digest will trigger at least two times to find the W that has watch equal to the last dirty check of the previous mark Atch to stop the dirty check//This also has the added benefit that when the last tagged watch is found, it jumps out of the loop and does not process the watch that has not changed after dirty = Fals E Break Traversescopesloop; }}} catch (e) {$exceptionHandler (e); }}}//This code function is a deep traversal, if (! ( Next = ((current.$ $watchersCount && current.$ $childHead) | | (Current!== target && current.$ $nextSibling)))) {while (current!== target &&!) ( Next = current.$ $nextSibling)) {current = current $parent; }}} and (current = next); ' Break traversescopesloop; ' takes us-here if (Dirty | | asyncqueue.length) &&! ( ttl--)) {clearphase (); The throw $rootScopeMinErr (' infdig ', ' {0} $digest () iterations reached. aborting!\n ' + ' watchers fired in the last 5 iterations: {1} ', TTL, Watchlog); }} while (Dirty | | asyncqueue.length); Clearphase (); while (Postdigestqueue.length) {try {postdigestqueue.shift () (); } catch (E) {$exceptionHandler (e); } } }
The above code, we need to focus on, do {...} while (Dirty | | asyncqueue.length); This is the entire digest loop, the details of which are commented in the code. You may wonder why it takes two times to digest. The reason is very simple, is to ensure that the digest behind the watcher change does not affect the front of the Watcher, play a role in verification. Below is an example:
//html<input type= "text" ng-model= "test"/>//js $scope. Test = "AAA"; $scope. $watch (' Test ', function (value,last) {
Filter the model data, try not to do so, because Ngmodelcontroller has a corresponding method. Here is just an example if (value!== last) { $scope. Test = "BBB"; } });
In the example above, when we change input, digest will trigger, three times.
Of course, the code is slightly modified $scope. Test = $scope. Test + ' 1 '; This digest will be more than 10 times, error.
Knowing how to do a dirty check, then we will continue to understand how to register the observed object in the Watchers of the Dirty check, there are two main categories:
- {{}},ngbind,nghide ...
- $watch ()
The first type of registration process:
- When compile, collect directive
- Link stage, using the $watch () binding
Take {{}} as an example
Collect the directive code. Add directive according to NodeTypefunctioncollectdirectives (node, directives, Attrs, maxpriority, ignoredirective) {varNodeType =Node.nodetype, Attrsmap=attrs. $attr, match, ClassName; Switch(NodeType) {
Case Node_type_text://Processing TEXT
if (msie = = = 11) {
Workaround for #11781
while (Node.parentnode && node.nextsibling && node.nextSibling.nodeType = = = Node_type_text) {
Node.nodevalue = Node.nodevalue + node.nextSibling.nodeValue;
Node.parentNode.removeChild (node.nextsibling);
}
}
Addtextinterpolatedirective (directives, node.nodevalue); }}
{{}} directive
function addtextinterpolatedirective (directives, text) {
var interpolatefn = $interpolate (text, true);
if (interpolatefn) {
Directives.push ({
priority:0,
Compile:function Textinterpolatecompilefn (Templatenode) {
var templatenodeparent = Templatenode.parent (),
Hascompileparent =!! Templatenodeparent.length;
When transcluding a template, the with bindings in the root
We don ' t have a parent and thus need to add the class during linking FN.
if (hascompileparent) compile.$ $addBindingClass (templatenodeparent);
return function TEXTINTERPOLATELINKFN (scope, node) {
var parent = node.parent ();
if (!hascompileparent) compile.$ $addBindingClass (parent);
compile.$ $addBindingInfo (parent, interpolatefn.expressions);
adding monitoring via $watch
Scope. $watch (Interpolatefn, function interpolatefnwatchaction (value) {
Node[0].nodevalue = value;
});
};
}
});
}
}
Next we need to talk about $watch. After all, some instructions and manually registering watch are all through this method. Or look at the source code
$watch:function(watchexp, Listener, objectequality, prettyprintexpression) {varget =$parse (WATCHEXP); $parse Conversionif(get.$ $watchDelegate) {//If Watch has been registered, remove watch operationreturnget.$ $watchDelegate ( This, Listener, objectequality, get, watchexp); } varScope = This, Array=scope.$ $watchers, watcher={fn:listener, last:initwatchval, Get:get, Exp:prettyprintexp Ression||watchexp, eq:!!objectequality}; Lastdirtywatch=NULL; if(!isfunction (Listener)) {Watcher.fn=NoOp; } if(!Array) {Array= scope.$ $watchers = []; }//unshift is because, Digest is length--, so before registering to the Watchers arrayArray.unshift (Watcher); Incrementwatcherscount ( This, 1); Modify the number of Watcherscountreturn functionDeregisterwatch () {if(Arrayremove (array, watcher) >= 0) {Incrementwatcherscount (scope,-1); } Lastdirtywatch=NULL; Delete Dirty check watch, Prevent dead loop}; },
$watchGroup, $watchCollection is the extension of $watch, which is mainly the listener that changes the properties of the array and the object. Specifically how to use, we can try, do not introduce.
Of course, we often encounter $apply () and manually trigger the $digest loop. The code is simple. Don't introduce it.
In angular, dirty checking takes up a large part of the JS operation, especially when a drop-down wireless list is used to bind a large number of elements. This time we need to consider reducing $watch and reducing the digest cycle. Bindonce, for example, is to unregister watch after binding an element to a page.
Bidirectional binding of the Angularjs series