Introducción
El alcance se encuentra en una posición central en el ecosistema NG. La capa subyacente de la vinculación bidireccional reclamada por Ng al mundo exterior en realidad es implementada por el alcance. Este capítulo analiza principalmente el mecanismo de vigilancia del alcance, la herencia y la implementación de eventos.
monitor
1. $ Reloj
1.1 Uso
// $ reloj: function (watchexp, oyente, objectEquality)
var unwatch = $scope.$watch('aa', function () {}, isEqual);
Aquellos que han usado Angular a menudo usan el código anterior, comúnmente conocido como "manualmente" agregando escucha, y algunos otros oyentes agregan automáticamente la escucha a través de la interpolación o la directiva, pero en principio son iguales.
1.2 Análisis del código fuente
function (watchEXP, oyerer, objetualEquality) {var scope = this, // compile posibles cadenas en fn get = compilToFn (watchexp, 'watch'), array = scope. $$ watchers, watcher = {fn: oyente, la última: initwatchval, // el valor de la configuración de la próxima comparación de la próxima comparación: obtener, exp: exp: EXP, EQ: EQ: !! comparación}; lastdirtywatch = null; if (! isfunction (oyeur)) {var escucharfn = compiletofn (oyente || noop, 'oyente'); Watcher.fn = function (newval, Oldval, Scope) {Listenfn (alcance);}; } if (! Array) {array = scope. $$ Watchers = []; } // La razón por la que no se empuja el desplazamiento es porque en $ Digest el bucle de observadores comienza desde la parte posterior/ para habilitar que el observador recién agregado se ejecute en el bucle actual, se coloca en la parte delantera de la cola.unshift (observador); // return unwatchfn, cancele la función de retorno de escucha deRegisterSwatch () {ArrayRemove (Array, Watcher); lastdirtywatch = null; };}Del código, $ Watch es relativamente simple. Principalmente guarda al observador en la matriz de observadores $$
2. $ Digest
Cuando el valor del alcance cambia, el alcance no ejecutará cada oyente de observador por sí mismo. Debe haber una notificación, y el que envía esta notificación es $ Digest
2.1 Análisis del código fuente
El código fuente de $ Digest $ es de aproximadamente 100 líneas, y la lógica principal se concentra en el bucle de verificación sucia. También hay algunos códigos menores después del bucle, como el procesamiento de PostDigestqueue, por lo que no analizaremos en detalle.
El bucle de verificación de valor sucio significa que mientras haya una actualización del valor de un observador, se debe ejecutar una ronda de verificaciones hasta que no se actualicen actualizaciones de valor. Por supuesto, se han realizado algunas optimizaciones para reducir los controles innecesarios.
Código:
// Ingrese el bucle $ Digest y marquéelo para evitar la entrada repetida en BeginPhase ('$ Digest'); lastDirtyWatch = null; // El bucle de verificación de valor sucio comienza a hacer {Dirty = false; actual = Target; // El bucle Asyncqueue omite TraververSescopesloop: do {if ((Watchers = Current. $$ Watchers)) {longitud = Watchers.length; while (longitud--) {try {watch = watchers [longitud]; if (watch) {// realice una actualización para determinar si hay un valor actualizado, descompone lo siguiente // value = watch.get (actual), último = watch.last // value! == Last Si es verdadero, entonces determine si es necesario hacer un valor para juzgar watch.eq? iguales (valor, lo último) // si no es un juicio de los valores iguales, determine la situación de Nan, es, es, es, Nan! watch.get (actual))! == (last = watch.last) &&! (watch.eq? Equals (valor, último): (typeof value === 'number' && typeof last ==== 'number' && isnan (valor) && isnan (last)))) {Dirty = true; // Grabar cuyo reloj cambia en este bucle LastDirtyWatch = watch; // caché Último valor watch.last = watch.eq? copia (valor, nulo): valor; // Ejecutar LOYERFN (NewValue, LastValue, Scope) // Si se realiza la primera ejecución, luego LastValue también se establece en NewValue Watch.fn (valor, ((last === InitWatchVal)? Value: Last), actual); // ... WatchLog omite if (watch.get. $$ untatch) stablewatchescandidates.push ({watch: watch, array: watchers}); } // Esta es la optimización para reducir el observador // Si el último reloj actualizado en el bucle anterior no ha cambiado, es decir, no hay un nuevo reloj actualizado en esta ronda //, entonces significa que todos los relojes han sido estables y no se actualizarán, y el bucle termina aquí. Los relojes restantes no necesitan ser verificados de lo contrario si (watch === lastdirtywatch) {sucio = false; Romper Traversescopesloop; }}} catch (e) {clearPhase (); $ ExceptionHandler (e); }}} // Esta sección está un poco enredada, lo que en realidad se está realizando la profundidad de la primera transferencia // a-> [b-> d, c-> e] // Orden de ejecución A, B, D, C, E // Obtenga el primer hijo cada vez. Si no hay un hermano de NextsiBling, si no hay hermano, retire a la capa anterior y determine si hay hermanos en la capa. Si no hay, continúe retirándose hasta que se retire al alcance inicial. En este momento, siguiente == nulo, por lo que el bucle de ámbitos se saldrá si (! (Next = (actual. $$ childhead || (actual! == Target && Current. $$ nextSibling))) {while (actual! == Target &&! (Next = Current. $$ nextSibling))) {actual = actual. $ Parent; }}} while ((actual = next)); // Break TraververSescopesloop va directamente aquí // Determine si todavía está en un bucle de valor sucio y ha excedido el número máximo de cheques TTL predeterminado 10 if ((sucio || asyncqueue.length) &&! (ttl--)) {clearPhase (); Lanzar $ RootsCopeminerr ('Infdig', '{0} $ Digest () iteraciones alcanzadas. ¡Abortando!/N' + 'observadores disparados en las últimas 5 iteraciones: {1}', ttl, tOjson (watchlog)); }} while (sucio || asyncqueue.length); // end del bucle // Mark Salida Digest Loop ClearPhase ();Hay bucles de 3 capas en el código anterior
La primera capa juzga sucia, si hay un valor sucio, continúa buceando
hacer {
// ...
} mientras (sucio)
La segunda capa determina si el alcance ha sido atravesado. El código ha sido traducido. Aunque todavía se distribuye, se puede entender.
hacer {
// ....
if (actual. $$ childhead) {
Siguiente = actual. $$ Childhead;
} else if (actual! == Target && Current. $$ nextSibling) {
Next = actual. $$ nextsibling;
}
while (! next && current! == target &&! (next = actual. $$ nextsibl)) {
actual = actual. $ parent;
}
} while (actual = siguiente);
La tercera capa de observadores de alcance de bucle
longitud = observadores.length;
mientras (longitud--) {
intentar {
watch = Watchers [longitud];
// ... omitido
} catch (e) {
ClearPhase ();
$ ExceptionHandler (e);
}
}
3. $ Evalasync
3.1 Análisis del código fuente
$ Evalasync se usa para la ejecución retrasada, el código fuente es el siguiente:
function (exp) {if (! $ rootscope. $$ fase &&! $ rootscope. $$ asyncqueue.length) {$ browser.defer (function () {if ($ rootscope. $$ asyncqueue.length) {$ rootscope. $ digest ();}); } this. $$ asyncqueue.push ({alcance: this, expresión: expr});}Juzgando si Dirty Check ya se está ejecutando, o alguien ha activado $ Evalasync
if (! $ rootscope. $$ fase &&! $ rootscope. $$ asyncqueue.length) $ browser.defer debe cambiar la orden de ejecución llamando a setTimeOut $ browser.defer (function () {// ...});Si no está usando diferir, entonces
function (exp) {queue.push ({alcance: this, expresión: exp}); this. $ digest ();} alcance. $ Evalasync (fn1); Scope. $ Evalasync (fn2); // El resultado es // $ digest ()> fn1> $ digest ()> fn2 // pero el efecto real debe lograrse: $ digest ()> fn1> fn2El contenido de Async se omitió en la sección anterior $ Digest, que se encuentra en la primera capa de bucle
while (asyncqueue.length) {try {asyncTask = asyncqueue.shift (); asynctask.scope. $ eval (asynctask.expression); } catch (e) {clearPhase (); $ ExceptionHandler (e); } lastdirtywatch = null;}Simple y fácil de entender, emerge AsyncTask para su ejecución.
Pero aquí hay un detalle, ¿por qué está configurado así? La razón es la siguiente. Si se agrega una nueva AsyncTask cuando el WATLX se ejecuta en un determinado bucle, lastDirtyWatch = WATLX se establecerá en este momento. La ejecución de esta tarea hará que se ejecute un nuevo valor en el reloj posterior de WatchX. Si no hay un código siguiente, entonces el bucle saltará del siguiente bucle a LastDirtyWatch (WatchX) y Dirty == falso en este momento.
lastdirtywatch = null;
También hay un detalle aquí, ¿por qué bucle en el primer nivel? Debido a que el alcance de la relación de herencia tiene $$ asyncqueue, todo está montado en la raíz, por lo que no es necesario ejecutarlo en la capa de alcance de la siguiente capa.
2. Herencia
El alcance es heredable, como $ Padrescope y $ Childscope Two Scopes. Cuando se llama a $ childscope.fn si no hay método FN en $ childscope, luego vaya a $ padrescope para encontrar el método.
Busque capa por capa hasta que encuentre el atributo requerido. Esta característica se implementa utilizando las características de la herencia prototipo de Javascipt.
Código fuente:
función (aislar) {var childscope, child; if (aisolado) {child = new Scope (); Child. $ root = this. $ root; // La Asyncqueue y Postdigestqueue del aislado también son raíz pública y otros niños independientes. $$ asyncqueue = this. $$ asyncqueue; Child. $$ Postdigestqueue = this. $$ Postdigestqueue; } else {if (! this. $$ childscopeclass) {this. $$ childscopeclass = function () {// Aquí podemos ver qué atributos son exclusivos de aislamiento, como los observadores de $$, por lo que serán monitoreados de forma independiente. This. $$$ Watchers = this. $$ nextsibling = this. $$ childhead = this. $$ childtail = null; this. $$ oyentes = {}; this. $$ ListenerCount = {}; this. $ id = nextUid (); this. $$ childscopeclass = null; }; this. $$ childscopeclass.prototype = this; } niño = nuevo this. $$ childscopeclass (); } // Establezca varias relaciones padre-hijo y hermano, ¡lo cual es muy desordenado! niño ['this'] = niño; niño. $ parent = this; Child. $$ Prevsible = this. $$ Childtail; if (this. $$ Childhead) {this. $$ Childtail. $$ nextsibling = child; esto. $$ Childtail = Child; } else {this. $$ childhead = this. $$ childtail = child; } niño de retorno;}El código es claro, los principales detalles son qué atributos deben ser independientes y cuáles deben basarse en lo básico.
El código más importante:
this.$$childScopeClass.prototype = this;
La herencia se realiza de esta manera.
3. Mecanismo de eventos
3.1 $ on
función (nombre, oyente) {var namedListeners = this. $$ oyentes [nombre]; if (? } namedListeners.push (oyente); var corriente = this; do {if (! Current. $$ ListenerCount [nombre]) {actual. $$ ListenerCount [name] = 0; } actual. $$ ListenerCount [nombre] ++; } while ((actual = actual. $ parent)); var self = this; function de return () {namedListeners [indexOf (namedListeners, oyente)] = null; DecrementListenCount (self, 1, nombre); };}Similar a $ Wathc, también se almacena en una matriz, nombradas Listeners.
Hay otra diferencia en que el alcance y todos los padres guardan una estadística de eventos, lo cual es útil al transmitir eventos y análisis posteriores.
var current = this; do {if (! Current. $$ ListenerCount [name]) {actual. $$ ListenerCount [name] = 0; } actual. $$ ListenerCount [nombre] ++;} while ((actual = actual. $ parent));3.2 $ emit
$ emit es un evento de transmisión ascendente. Código fuente:
function (name, args) {var vacía = [], namedListeners, scope = this, stopPropagation = false, event = {name: name, TargetScope: Scope, stopPropagation: function () {stopPropagation = true;}, prevenedefault: function () {event.defaultPreventent = true; }, defaultPrevented: false}, listyeroRargs = concat ([evento], argumentos, 1), i, longitud; do {namedlisteners = alcance. $$ oyentes [nombre] || vacío; event.currentscope = alcance; para (i = 0, longitud = namedListeners.length; i <longitud; i ++) {// Después de escuchar eliminar, no se eliminará de la matriz, sino que se establece en NULL, por lo que es necesario juzgar si (! NameListeners [i]) {chamuslisteners.splice (i, 1); i--; longitud--; continuar; } try {namedListeners [i] .Apply (null, overyeraRargs); } catch (e) {$ ExceptionHandler (e); }} // Cuando se detiene la propagación, regrese if (stopPropagation) {event.currentscope = null; evento de regreso; } // emit es la forma de propagar el alcance ascendente = alcance. $ parent; } while (alcance); event.currentscope = null; evento de regreso;}3.3 $ transmisión
$ Broadcast está propagando hacia adentro, es decir, propagarse al niño, código fuente:
function (name, args) {var target = this, current = target, next = target, event = {name: name, targetScope: target, previsedDefault: function () {event.defaultprevented = true; }, defaultprevented: false}, listyeroRargs = concat ([evento], argumentos, 1), oyentes, i, longitud; while ((current = next)) {event.currentscope = current; oyentes = actual. $$ oyentes [nombre] || []; para (i = 0, longitud = oyentes.length; i <longitud; i ++) {// verifique si la escucha ha sido cancelada if (! oyentes [i]) {oyentes.splice (i, 1); i--; longitud--; continuar; } try {oyentes [i] .Apply (NULL, LOYARERARGS); } catch (e) {$ ExceptionHandler (e); }} // if (next = ((actual. $$ ListenerCount [name] && Current. $$ Childhead) || (actual! == Target && Current. $$ NEGOJO))) {while (actual! == Target &&! (Next = Current. $$ NEGNOTSIBLE)) {actual = actual. $ Parent; }}} event.currentscope = null; evento de regreso;}La otra lógica es relativamente simple, es decir, el código que está atravesado en profundidad es más confuso. De hecho, es lo mismo que en Digest. Es juzgar si hay una escucha en el camino. Actual. $$ ListenerCount [nombre]. Desde el código de $ en el código anterior, podemos ver que mientras haya un niño en el camino y escuche, el encabezado de la ruta también tiene un número. Por el contrario, si no se dice que todos los niños en el camino no tienen eventos de escucha.
if (! (Next = (((Current. $$ ListenerCount [nombre] && Current. $$ Childhead) || (actual! == Target && Current. $$ nextSibling))) {while (actual! == Target &&! (Next = Current. $$ nextSibl)) {actual = actual. $ parent; }}Ruta de propagación:
Raíz> [a> [a1, a2], b> [b1, b2> [c1, c2], b3]]
Raíz> a> a1> a2> b> b1> b2> c1> c2> b3
4. $ WatchCollection
4.1 Usar ejemplos
$ scope.names = ['igor', 'matias', 'misko', 'james']; $ scope.dataCount = 4; $ scope. $ watchCollection ('nombres', function (newnames, oldnames) {$ scope.dataCount = Newnames.length;}); WIEP ($ Scope.DataCount) .Toequal (4); $ Scope. $ Digest (); Espere ($ Scope.DataCount) .toequal (4); $ Scope.names.pop (); $ Scope. $ Digest (); Suppion ($ Scope.datacount) .Toequal (3);4.2 Análisis del código fuente
función (obj, oyente) {$ watchCollectionInterceptor. $ Stateful = true; var self = this; var newValue; var OldValue; var muy el valor; var trackveryoldValue = (oyente.length> 1); var cambiaetected = 0; var cambiaTector = $ parse (obj, $ watchCollectionInterceptor); var internalArray = []; var internalObject = {}; var initrun = true; var OldLength = 0; // Determinar si se debe cambiar en función de la función cambiada devuelta $ watchCollectionInterceptor (_value) {// ... return cambiedEdect; } // Llame al oyente real a través de este método como una función proxy $ watchCollectionAction () {} Devuelve esto. $ Watch (Watchetector, $ WatchCollectionAction);}La vena principal es parte del código interceptado anteriormente. Lo siguiente analiza principalmente $ WatchCollectionInterceptor y $ WatchCollectionAction
4.3 $ WatchCollectionInterceptor
función $ watchCollectionInterceptor (_Value) {newValue = _Value; var newLength, Key, Bothnan, NewItem, OldItem; if (isundefined (newValue)) return; if (! isObject (newValue)) {if (OldValue! == NewValue) {OldValue = newValue; cambiado detectado ++; }} else if (isarraylike (newValue)) {if (OldValue! == interneralArray) {OldValue = internerray; OldLength = OldValue.length = 0; cambiado detectado ++; } newLength = newValue.length; if (OldLength! == NewLength) {cambiatected ++; OldValue.length = OldLength = newLength; } para (var i = 0; i <newLength; i ++) {OldItem = OldValue [i]; newItem = newValue [i]; bhynan = (OldItem! == OldItem) && (NewItem! == NewItem); if (? OldValue [i] = NewItem; }}} else {if (OldValue! == internalObject) {OldValue = internalObject = {}; OldLength = 0; cambiado detectado ++; } newLength = 0; para (clave en newValue) {if (durawnproperty.call (newValue, key)) {newLength ++; newItem = newValue [clave]; OldItem = OldValue [clave]; if (key en OldValue) {bothnan = (OldItem! == OldItem) && (NewItem! == NewItem); if (? OldValue [Key] = NewItem; }} else {OldLength ++; OldValue [Key] = NewItem; cambiado detectado ++; }}} if (OldLength> NewLength) {cambiatected ++; para (clave en OldValue) {if (! HasnownProperty.call (newValue, Key)) {OldLength--; eliminar OldValue [clave]; }}}} return cambiado detectado;}1). Regrese directamente cuando el valor esté indefinido.
2). Cuando el valor es un tipo básico ordinario, determine directamente si es igual.
3). Cuando el valor es una matriz de clase (es decir, el atributo de longitud existe, y el valor [i] también se llama matriz de clase), el Valor OldValue se inicializa primero sin inicialización
if (OldValue! == InternalArray) {OldValue = internerray; OldLength = OldValue.length = 0; cambiado ++;}Luego compare la longitud de la matriz, si no es igual, se registra como cambiando Detected ++
if (OldLength! == NewLength) {cambiatected ++; OldValue.length = OldLength = newLength;}Compare uno por uno
for (var i = 0; i <newLength; i ++) {OldItem = OldValue [i]; newItem = newValue [i]; bhynan = (OldItem! == OldItem) && (NewItem! == NewItem); if (? OldValue [i] = NewItem; }}4). Cuando el valor es objeto, el proceso de inicialización es similar al anterior
if (OldValue! == internalObject) {OldValue = internalObject = {}; OldLength = 0; cambiado ++;}El siguiente procesamiento es más hábil. Si encuentra que se agregan nuevos campos con muchos NewValue, agregue 1 a OldLength, de modo que OldLength solo agrega y no reste. Es fácil encontrar si hay nuevos campos en NewValue. Finalmente, elimine los campos adicionales en OldValue, es decir, los campos eliminados en NewValue, y luego ha terminado.
newLength = 0; for (Key in NewValue) {if (HasnownProperty.call (newValue, Key)) {newLength ++; newItem = newValue [clave]; OldItem = OldValue [clave]; if (key en OldValue) {bothnan = (OldItem! == OldItem) && (NewItem! == NewItem); if (? OldValue [Key] = NewItem; }} else {OldLength ++; OldValue [Key] = NewItem; cambiado detectado ++; }}} if (OldLength> NewLength) {cambiatected ++; para (clave en OldValue) {if (! HasnownProperty.call (newValue, Key)) {OldLength--; eliminar OldValue [clave]; }}}4.4 $ watchCollectionAction
function $ watchCollectionAction () {if (initRun) {initRun = false; oyente (NewValue, NewValue, Self); } else {oyente (newValue, muyeDValue, self); } // TrackVersyLoldValue = (LOYER.LIGTH> 1) Compruebe si el método del oyente requiere OldValue // Copiar si es necesario si (TrackVerlyValue) {if (! ISObject (newValue)) {muyeDValue = newValue; } else if (isarraylike (newValue)) {muyeDValue = new Array (newValue.length); for (var i = 0; i <newValue.length; i ++) {muyeDValue [i] = newValue [i]; }} else {muyeDValue = {}; para (var key var en newValue) {if (haownproperty.call (newValue, key)) {muyeDValue [key] = newValue [key]; }}}}}}El código es relativamente simple, es llamar a Listenerfn. Cuando la primera llamada es OldValue == NewValue. Para la eficiencia y la memoria, se determina si el oyente necesita el parámetro OldValue.
5. $ eval y $ aplicar
$ eval: function (expr, locals) {return $ parse (expr) (this, locals);}, $ apply: function (expr) {try {beginphase ('$ aplicar'); devolver esto. $ eval (expr); } catch (e) {$ ExceptionHandler (e); } Finalmente {ClearPhase (); Pruebe {$ RootsCope. $ Digest (); } catch (e) {$ ExceptionHandler (e); tirar E; }}}$ Aplicar finalmente llama $ ROOTSCOPE. $ Digest (), por lo que muchos libros recomiendan usar $ Digest () en lugar de llamar a $ Aplicar (), que es más eficiente.
La lógica principal está todo en $ parse, que pertenece a la función de análisis de sintaxis, y se analizará por separado en el futuro.