Angularjs Source Analysis of Scope_angularjs

Source: Internet
Author: User
Tags comparison emit eval inheritance hasownproperty


Brief introduction



In Ng's ecology, scope is at a core position, the bottom of the two-way binding of NG is actually the scope of implementation, this chapter mainly on the scope of the watch mechanism, inheritance and the implementation of the event for analysis.



Listening



1. $watch



1.1 Use



$watch: Function (watchexp, listener, objectequality)



var unwatch = $scope.$watch('aa', function () {}, isEqual);



Use of angular will often the above code, commonly known as "manual" to add listening, others are interpolation or directive automatically add listening, but the principle is the same.



1.2 Source Analysis


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 record, Convenient next time compare
    Get:get,
    exp:watchexp,
    eq:!! Objectequality//configuration is reference comparison or value comparison
   };

 Lastdirtywatch = null;

 if (!isfunction (listener)) {
  var listenfn = compiletofn (Listener | | noop, ' listener ');
  Watcher.fn = function (newval, oldval, scope) {LISTENFN (scope);}

 if (!array) {
  array = scope.$ $watchers = [];
 }
 
 The reason why using unshift is not push is because in $digest the Watchers loop is started from the back
 /in order to make the newly added watcher can also be executed in the secondary loop so that it is placed at the front of the queue
 Array.unshift ( Watcher);

 Returns the UNWATCHFN, suppresses the listener return
 function Deregisterwatch () {
  arrayremove (array, watcher);
  Lastdirtywatch = null;}
 ;
}


From the code $watch is still relatively simple, mainly to save the watcher to the $ $watchers array



2. $digest



When the value of scope changes, scope does not perform each watcher LISTENERFN itself, and must have a notification, and the notification is sent $digest



2.1 Source Analysis



The entire $digest source code is almost 100 lines, the main logic is concentrated in the "Dirty Check Loop" (Dirty check loop), after the loop also some minor code, such as postdigestqueue processing and so do not make detailed analysis.



The dirty value checks the loop, meaning that as long as there is an update on a watcher value, a round of checks is run until no value is updated, and of course some optimizations are made to reduce unnecessary checks.



Code:


Enter the $digest cycle marking to prevent repeated entry into the Beginphase (' $digest ');

Lastdirtywatch = null;
 Dirty value Check loop start do {dirty = false;

 current = target;
   Asyncqueue Loops Omit Traversescopesloop:do {if (watchers = current.$ $watchers)) {length = Watchers.length;
     while (length--) {try {watch = Watchers[length]; if (watch) {//update to determine if there is a value update, the breakdown is as follows//value = Watch.get (current), last = watch.last//value!== , determine whether a value should be judged watch.eq?equals (value, last)//if it is not a value equality judgment, the case of Nan!== nan if (value = Watch.get (curren T)!== (last = watch.last) &&!
            (Watch.eq equals (value, last): (typeof value = = ' Number ' && typeof last = = ' number '
       && isNaN (value) && isNaN (last))) {dirty = true;
       Record which watch in this cycle change lastdirtywatch = watch; Cache last Value Watch.last = Watch.eq?
       Copy (value, NULL): value;
     Executive LISTENERFN (NewValue, Lastvalue, scope)  If it is executed for the first time, then Lastvalue is also set to NewValue Watch.fn ((last = = = Initwatchval), current); ... watchlog omit if (watch.get.$ $unwatch) Stablewatchescandidates.push ({watch:watch, Array:wat
      Chers}); //This is the reduction of watcher optimization//If the last update of the previous cycle watch has not changed, that is, there is no new updated watch//Then the whole watches has been stable will not be updated, this cycle is over, leaving
       Watch, you don't have to check for else if (watch = = Lastdirtywatch) {dirty = false;
      Break Traversescopesloop;
     catch (E) {clearphase ();
    $exceptionHandler (e); }}//This is a bit of a detour, in fact, to achieve depth first traversal//a->[b->d,c->e]///execution order A,b,d,c,e//each priority to get first child, if not then get Nextsibli Ng Brothers, if even the brothers are gone, then back to the upper level and determine whether the layer has brothers, no words continue to retreat until the beginning of the scope, then Next==null, so will exit the scopes cycle if (!
    Next = (current.$ $childHead | | (Current!== target && current.$ $nextSibling))) {while, current!== target &&! (
   Next = current.$ $nextSibling)) {current = current. $parent; }}} WHile ((current = next)); Break Traversescopesloop directly to this side//judge is not still in the dirty value loop, and has exceeded the maximum number of checks TTL default ten if (Dirty | | asyncqueue.length) &&! (
  ttl--)) {clearphase (); Throw $rootScopeMinErr (' infdig ', ' {0} ' $digest () iterations reached.
 aborting!\n ' + ' watchers fired in the last 5 iterations: {1} ', TTL, Tojson (Watchlog)); } while (Dirty | | asyncqueue.length);

 Loop End/Mark Exit Digest Cycle Clearphase ();


There are 3 layers of loops in the above code



The first level of judgment dirty, if there is dirty value then continue to cycle



do {



// ...



} while (dirty)



The second level determines whether scope has been traversed, the code translated, although still around but can understand



do {



// ....



if (current.$ $childHead) {
Next = current.$ $childHead;
else if (current!== target && current.$ $nextSibling) {
Next = current.$ $nextSibling;
}
while (!next &&!== target &&! ( Next = current.$ $nextSibling)) {
Current = current. $parent;
}
while (current = next);



Watchers of the third-tier cycle scope



length = Watchers.length;
while (length--) {
try {
Watch = Watchers[length];

// ... Omitted



catch (e) {
Clearphase ();
$exceptionHandler (e);
}
}



3. $evalAsync



3.1 Source Analysis



$evalAsync used for deferred execution, the source code is as follows:


function (expr) {
 if (! $rootScope. $ $phase &&! $rootScope. $ $asyncQueue. Length) {
  $browser. Defer ( function () {
   if ($rootScope. $ $asyncQueue. Length) {
    $rootScope. $digest ();}}
  );

 this.$ $asyncQueue. Push ({scope:this, expression:expr});



By judging if there is already a dirty check running, or someone has already triggered the $evalasync


if (! $rootScope. $ $phase &&! $rootScope. $ $asyncQueue. Length)
$browser. Defer is to change the order of execution by calling SetTimeout 

$browser. Defer (function () {
 //...   
});


If you are not using defer, then


Function (exp) {
 Queue.push ({scope:this, expression:exp});

 this. $digest ();
}

Scope. $evalAsync (FN1);
Scope. $evalAsync (FN2);

The result is
//$digest () > Fn1 > $digest () > Fn2
//But the actual effect needs to be achieved: $digest () > Fn1 > Fn2


The contents of the async are omitted from the $digest in the previous section and are located in the first layer loop


while (asyncqueue.length) {
 try {
  asynctask = Asyncqueue.shift ();
  Asynctask.scope. $eval (asynctask.expression);
 catch (e) {
  clearphase ();
  $exceptionHandler (e);
 }
 Lastdirtywatch = null;
}


Easy to understand, pop-up asynctask for execution.



But there is a detail here, why is it so set up? The reason for this is that if you add 1 asynctask to Watchx in a loop, then Lastdirtywatch=watchx is set up, and that task execution causes a WATCHX subsequent watch to execute the new value, if there is no code below, Then the next loop to Lastdirtywatch (WATCHX) jumps out of the loop and dirty==false at this point.



Lastdirtywatch = null;



There is also a detail here, why in the first layer of circulation? Because the scope of the inheritance relationship is $ $asyncQueue common, it is mounted on root and does not need to be executed at the next level of the scope layer.



2. The nature of inheritance



Scope has inheritance, such as $parentScope, $childScope two scope, and when $childScope. Fn is called, if the $childScope does not have the FN method, then the method is found on the $parentScope.



This layer looks up until the desired attribute is found. This feature is implemented using the characteristics of Javascirpt's prototype inheritance.



Source:


function (isolate) {var childscope, child;
  if (isolate) {child = new Scope ();
  Child, $root = this. $root;
  The Asyncqueue and postdigestqueue of isolate are also common root, and other independent child.$ $asyncQueue = this.$ $asyncQueue;
 child.$ $postDigestQueue = this.$ $postDigestQueue; } else {if (!this.$ $childScopeClass) {this.$ $childScopeClass = function () {//) Here you can see which properties are isolated, such as $ $watchers, so
    On the independent monitoring, this.$ $watchers = this.$ $nextSibling = this.$ $childHead = this.$ $childTail = null;
    this.$ $listeners = {};
    this.$ $listenerCount = {};
    this. $id = Nextuid ();
   this.$ $childScopeClass = null;
   };
  this.$ $childScopeClass. prototype = this;
 Child = new this.$ $childScopeClass ();
 //Set up a variety of father and son, brother relationship, very messy!
 Child[' this '] = child;
 Child, $parent = this;
 child.$ $prevSibling = this.$ $childTail;
  if (this.$ $childHead) {this.$ $childTail. $ $nextSibling = child;
 this.$ $childTail = child;
 else {this.$ $childHead = this.$ $childTail = child;
return to child;

 }


The code is also clear that the main details are which attributes need to be independent and which need to be based.



The most important code:



this.$$childScopeClass.prototype = this;



That's how inheritance is done.



3. Event mechanisms



3.1 $on


function (name, listener) {
 var namedlisteners = this.$ $listeners [name];
 if (!namedlisteners) {
  this.$ $listeners [name] = Namedlisteners = [];
 }
 Namedlisteners.push (listener);

 var current = this;
 Do {
  if (!current.$ $listenerCount [name]) {
   current.$ $listenerCount [name] = 0;
  }
  current.$ $listenerCount [name]++;
 } while (current = current. $parent));

 var self = this;
 return function () {
  namedlisteners[indexof (namedlisteners, listener)] = null;
  Decrementlistenercount (self, 1, name);}
 ;
}


Similar to $WATHC, it is stored in an array--namedlisteners.



There's a different place. This scope and all of the parent have saved the statistics of an event, which is useful when broadcasting events, and subsequent analysis.


var current = this;
Do {
 if (!current.$ $listenerCount [name]) {
  current.$ $listenerCount [name] = 0;
 }
 current.$ $listenerCount [name]++;
} while (current = current. $parent));


3.2 $emit



$emit is to broadcast events up. Source:


function (name, args) {var empty = [], namedlisteners, scope = this, Stoppropagation = False, event = { Name:name, Targetscope:scope, Stoppropagation:function () {stoppropagation = true;}, Preventdefault:functio
    N () {event.defaultprevented = true;

 }, Defaultprevented:false}, Listenerargs = Concat ([Event], arguments, 1), I, length;
  do {namedlisteners = scope.$ $listeners [name] | | empty;
  Event.currentscope = scope; For (i=0, Length=namedlisteners.length, i<length; i++) {//When the monitor remove is not removed from the array, it is set to NULL, so you need to judge if (!namedliste
    Ners[i]) {Namedlisteners.splice (I, 1);
    i--;
    length--;
   Continue
   try {namedlisteners[i].apply (null, Listenerargs);
   catch (E) {$exceptionHandler (e);
   The return if (stoppropagation) {event.currentscope = null when stopping propagation;
  return event;
 //emit is the upward propagation mode scope = scope. $parent;

 while (scope);

 Event.currentscope = null;
return event;}

 


3.3 $broadcast



$broadcast is to spread inward, that is, to child transmission, source code:


function (name, args) {var target = this, current = target, next = target, event = {Name:name, target
    Scope:target, Preventdefault:function () {event.defaultprevented = true;

 }, Defaultprevented:false}, Listenerargs = Concat ([Event], arguments, 1), listeners, I, length;
  while (current = next) {Event.currentscope = current; listeners = current.$ $listeners [name] | |
  []; For (i=0, length = listeners.length; i<length; i++) {//Check if (!listeners[i]) {Listeners.splic has been canceled
    E (I, 1);
    i--;
    length--;
   Continue
   try {listeners[i].apply (null, Listenerargs);
   catch (E) {$exceptionHandler (e); The IF (!) is already in the digest.
    Next = ((current.$ $listenerCount [name] && current.$ $childHead) | | (Current!== target && current.$ $nextSibling))) {while, current!== target &&! (
   Next = current.$ $nextSibling)) {current = current. $parent; }} Event. CurrentScope = null;
return event;

 }


Other logic is relatively simple, that is, in depth traversal of the code comparison around, in fact, as in Digest, is more in the path to determine whether there is listening, current.$ $listenerCount [name], from the above $on code, As long as there is a child on the path listening, the path header is also numeric, and conversely, if there is no indication that all the child on the path does not have a listener event.


if (!) ( Next = ((current.$ $listenerCount [name] && current.$ $childHead) | |
    (Current!== target && current.$ $nextSibling))) {While
 , current!== target &&! ( Next = current.$ $nextSibling)) {Current
  = current. $parent;
 }
}}


Propagate path:



ROOT>[A>[A1,A2], B>[B1,B2>[C1,C2],B3]]



Root > A > a1 > a2 > B > B1 > B2 > C1 > C2 > B3



4. $watchCollection



4.1 Using the example


$scope. Names = [' Igor ', ' Matias ', ' Misko ', ' James '];
$scope. datacount = 4;

$scope. $watchCollection (' names ', function (Newnames, oldnames) {
 $scope. datacount = newnames.length;
});

Expect ($scope. Datacount). toequal (4);
$scope. $digest ();

Expect ($scope. Datacount). toequal (4);

$scope. Names.pop ();
$scope. $digest ();

Expect ($scope. Datacount). toequal (3);


4.2 Source Analysis


function (obj, listener) {
 $watchCollectionInterceptor. $stateful = true;
 var self = this;
 var newvalue;
 var OldValue;
 var Veryoldvalue;
 var trackveryoldvalue = (Listener.length > 1);
 var changedetected = 0;
 var changedetector = $parse (obj, $watchCollectionInterceptor); 
 var internalarray = [];
 var internalobject = {};
 var initrun = true;
 var oldlength = 0;

 Determines whether the
 function $watchCollectionInterceptor (_value) {
  //...
  ) is changed according to the returned changedetected. return changedetected;
 }

 Call real listener through this method, as Agent
 function $watchCollectionAction () {} return this

 . $watch (Changedetector, $watchCollectionAction);
}


The main thread is the part of the code intercepted above, the following main analysis $watchCollectionInterceptor and $watchCollectionAction



4.3 $watchCollectionInterceptor


function $watchCollectionInterceptor (_value) {newvalue = _value;

 var newlength, Key, Bothnan, NewItem, Olditem;

 if (isundefined (NewValue)) return;
   if (!isobject (NewValue)) {if (OldValue!== newvalue) {oldValue = newvalue;
  changedetected++;
   } else if (Isarraylike (NewValue)) {if (OldValue!== internalarray) {oldValue = Internalarray;
   oldlength = oldvalue.length = 0;
  changedetected++;

  } newlength = Newvalue.length;
   if (oldlength!== newlength) {changedetected++;
  Oldvalue.length = Oldlength = Newlength;
   for (var i = 0; i < newlength i++) {olditem = Oldvalue[i];

   NewItem = Newvalue[i];
   Bothnan = (Olditem!== olditem) && (NewItem!==);
    if (!bothnan && (Olditem!== newitem)) {changedetected++;
   Oldvalue[i] = NewItem;
   }} else {if (OldValue!== internalobject) {OldValue = Internalobject = {};
   oldlength = 0;
  changedetected++;
  } newlength = 0; For (key in NewValue) {if (Hasownproperty.call (NewValue, key)) {newlength++;
    NewItem = Newvalue[key];

    Olditem = Oldvalue[key];
     if (key in OldValue) {Bothnan = (olditem!== olditem) && (NewItem!==);
      if (!bothnan && (Olditem!== newitem)) {changedetected++;
     Oldvalue[key] = NewItem;
     } else {oldlength++;
     Oldvalue[key] = NewItem;
    changedetected++;
   }} if (Oldlength > Newlength) {changedetected++;
     For (key in OldValue) {if (!hasownproperty.call (NewValue, key)) {oldlength--;
    Delete Oldvalue[key];
}}} return changedetected;

 }


1). Returns directly when the value is undefined.



2. The direct judgment is equal when the normal basic type is on duty.



3. The value is an array of classes (that is, there is a length property, and Value[i] is also called a class array), first initialization is not initialized oldvalue


if (OldValue!== internalarray) {
 oldValue = Internalarray;
 oldlength = oldvalue.length = 0;
 changedetected++;
}


The length of the array is then compared to the range of words that have changed changedetected++


if (oldlength!== newlength) {
 changedetected++;
 Oldvalue.length = Oldlength = Newlength;
}


And then compare it one by one


for (var i = 0; i < newlength i++) {
 olditem = oldvalue[i];
 NewItem = Newvalue[i];

 Bothnan = (Olditem!== olditem) && (NewItem!==);
 if (!bothnan && (Olditem!== newitem)) {
  changedetected++;
  Oldvalue[i] = NewItem
 }
}


4. When the value is object, similar to the above initialization processing


if (OldValue!== internalobject) {
 OldValue = Internalobject = {};
 oldlength = 0;
 changedetected++;
}


The next processing is more skillful, whenever found newvalue many new fields, in Oldlength plus 1, so that oldlength only add not minus, it is easy to find out whether there is a new newvalue field, and finally put out the oldvalue of the field is The deleted field in the NewValue is removed and ends.


newlength = 0;
For (key in NewValue) {
 if (Hasownproperty.call (NewValue, key)) {
  newlength++;
  NewItem = Newvalue[key];
  Olditem = Oldvalue[key];

  if (key in OldValue) {
   Bothnan = (olditem!== olditem) && (NewItem!==);
   if (!bothnan && (Olditem!== newitem)) {
    changedetected++;
    Oldvalue[key] = NewItem
   }
  } else {
   oldlength++;
   Oldvalue[key] = NewItem;
   changedetected++
}
}} if (Oldlength > Newlength) {
 changedetected++;
 For (key in OldValue) {
  if (!hasownproperty.call (NewValue, key)) {
   oldlength--;
   Delete Oldvalue[key];}}



4.4 $watchCollectionAction


function $watchCollectionAction () {
 if (initrun) {
  initrun = false;
  Listener (NewValue, newvalue, self);
 } else {
  Listener (newvalue, veryoldvalue, self);
 }

 Trackveryoldvalue = (Listener.length > 1) See if the listener method requires oldvalue
 //If necessary to replicate if
 (Trackveryoldvalue) {
  if (!isobject (NewValue)) {
   veryoldvalue = newvalue;
  } else if (Isarraylike (NewValue)) {
   Veryoldvalue = new Array (newvalue.length);
   for (var i = 0; i < newvalue.length i++) {
    veryoldvalue[i] = Newvalue[i];
   }
  } else { 
   veryoldvalue = {};
   for (var key in NewValue) {
    if (Hasownproperty.call (NewValue, key)) {
     Veryoldvalue[key] = Newvalue[key];
    }
   }
  }
 }
}


The code is relatively simple, that is, call LISTENERFN, the first call OldValue = = newvalue, for efficiency and memory to determine whether the next listener need oldValue parameters



5. $eval & $apply


$eval: function (expr, locals) {return
 $parse (expr) (this, locals);
},
$apply: function (expr) {
 try {
  beginphase (' $apply ');
  return this. $eval (expr);
 catch (e) {
  $exceptionHandler (e);
 } finally {
  clearphase ();
  try {
   $rootScope. $digest ();
  } catch (e) {
   $exceptionHandler (e);
   throw e;
  }}}


$apply the last call to $rootScope. $digest (), so many books recommend using $digest () instead of calling $apply (), which is more efficient.



The main logic is that the $parse belongs to the parsing function, followed by individual analysis.


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.