javascript - tutorial - ¿Cómo funciona el enlace de datos en AngularJS?




tutorial de angularjs en español (10)

¿Cómo funciona el enlace de datos en el marco de AngularJS ?

No he encontrado detalles técnicos en su sitio . Es más o menos claro cómo funciona cuando los datos se propagan de la vista al modelo. Pero, ¿cómo realiza AngularJS el seguimiento de los cambios de las propiedades del modelo sin configuradores y captadores?

Encontré que hay observadores de JavaScript que pueden hacer este trabajo. Pero no son compatibles con Internet Explorer 6 e Internet Explorer 7 . Entonces, ¿cómo sabe AngularJS que cambié, por ejemplo, lo siguiente y reflejé este cambio en una vista?

myobject.myproperty="new value";

Comprobando sucio el objeto $scope

Angular mantiene una array simple de observadores en los objetos $scope . Si inspecciona cualquier $scope , encontrará que contiene una array llamada $$watchers .

Cada observador es un object que contiene entre otras cosas.

  1. Una expresión que el observador está monitoreando. Esto podría ser solo un nombre de attribute , o algo más complicado.
  2. Un último valor conocido de la expresión. Esto se puede comparar con el valor calculado actual de la expresión. Si los valores difieren, el observador activará la función y marcará el $scope como sucio.
  3. Una función que se ejecutará si el observador está sucio.

Cómo se definen los vigilantes

Hay muchas maneras diferentes de definir un observador en AngularJS.

  • Puedes explícitamente $watch un attribute en $scope .

    $scope.$watch('person.username', validateUnique);
    
  • Puede colocar una interpolación {{}} en su plantilla (se creará un observador para usted en el $scope actual).

    <p>username: {{person.username}}</p>
    
  • Puede solicitar una directiva como ng-model para definir el observador por usted.

    <input ng-model="person.username" />
    

El ciclo $digest compara a todos los observadores contra su último valor

Cuando interactuamos con AngularJS a través de los canales normales (ng-model, ng-repeat, etc.) la directiva activará un ciclo de resumen.

Un ciclo de resumen es un recorrido primero en profundidad de $scope y todos sus hijos . Para cada object $scope , iteramos sobre su array $$watchers y evaluamos todas las expresiones. Si el nuevo valor de expresión es diferente del último valor conocido, se llama a la función del observador. Esta función podría recompilar parte del DOM, volver a calcular un valor en $scope , desencadenar una request AJAX , cualquier cosa que necesite hacer.

Cada ámbito se recorre y cada expresión de observación se evalúa y se compara con el último valor.

Si se activa un observador, el $scope está sucio.

Si se activa un observador, la aplicación sabe que algo ha cambiado y el $scope está marcado como sucio.

Las funciones del observador pueden cambiar otros atributos en $scope o en un $scope parent. Si se ha activado una función $watcher , no podemos garantizar que nuestros otros $scope s todavía estén limpios, por lo que ejecutamos todo el ciclo de resumen nuevamente.

Esto se debe a que AngularJS tiene enlace bidireccional, por lo que los datos se pueden pasar de nuevo al árbol de $scope . Podemos cambiar un valor en un $scope mayor de $scope que ya ha sido digerido. Quizás cambiemos un valor en el $rootScope .

Si el $digest está sucio, ejecutamos todo el ciclo $digest nuevamente

Continuamente recorremos el ciclo de $digest hasta que el ciclo de digestión sale limpio (todas las expresiones de $watch tienen el mismo valor que tenían en el ciclo anterior), o alcanzamos el límite de digestión. De forma predeterminada, este límite se establece en 10.

Si alcanzamos el límite de resumen, AngularJS generará un error en la consola:

10 $digest() iterations reached. Aborting!

El compendio es duro para la máquina pero fácil para el desarrollador

Como puede ver, cada vez que algo cambia en una aplicación de AngularJS, AngularJS verificará a cada observador en la jerarquía de $scope para ver cómo responder. Para un desarrollador, esto es un beneficio masivo de productividad, ya que ahora necesita escribir casi sin código de cableado, AngularJS solo notará si un valor ha cambiado y hará que el resto de la aplicación sea consistente con el cambio.

Desde la perspectiva de la máquina, esto es muy ineficiente y ralentizará nuestra aplicación si creamos demasiados observadores. Misko ha citado una cifra de alrededor de 4000 observadores antes de que su aplicación se sienta lenta en los navegadores más antiguos.

Es fácil llegar a este límite si, por ejemplo, ng-repeat en una array JSON grande. Puede mitigar esto utilizando características como el enlace único para compilar una plantilla sin crear observadores.

Cómo evitar crear demasiados observadores.

Cada vez que su usuario interactúa con su aplicación, cada observador en su aplicación será evaluado al menos una vez. Una gran parte de la optimización de una aplicación AngularJS es reducir el número de observadores en su árbol de $scope . Una forma fácil de hacer esto es con un enlace de tiempo .

Si tiene datos que raramente cambiarán, puede vincularlos solo una vez utilizando la :: sintaxis, así:

<p>{{::person.username}}</p>

o

<p ng-bind="::person.username"></p>

El enlace solo se activará cuando la plantilla que contiene se represente y los datos se carguen en $scope .

Esto es especialmente importante cuando tiene una ng-repeat con muchos elementos.

<div ng-repeat="person in people track by username">
  {{::person.username}}
</div>

  1. El enlace de datos unidireccional es un enfoque en el que se toma un valor del modelo de datos y se inserta en un elemento HTML. No hay manera de actualizar el modelo desde la vista. Se utiliza en sistemas de plantillas clásicas. Estos sistemas enlazan datos en una sola dirección.

  2. El enlace de datos en aplicaciones angulares es la sincronización automática de datos entre el modelo y los componentes de vista.

El enlace de datos le permite tratar el modelo como la única fuente de verdad en su aplicación. La vista es una proyección del modelo en todo momento. Si se cambia el modelo, la vista refleja el cambio y viceversa.


AngularJS recuerda el valor y lo compara con un valor anterior. Esto es una comprobación de suciedad básica. Si hay un cambio en el valor, entonces se dispara el evento de cambio.

El método $apply() , que es lo que llamas cuando estás haciendo la transición de un mundo que no es AngularJS a un mundo AngularJS, llama $digest() . Un compendio es simplemente viejo control sucio. Funciona en todos los navegadores y es totalmente predecible.

Para contrastar la comprobación de suciedad (AngularJS) frente a los oyentes de cambio ( KnockoutJS y Backbone.js ): Aunque la comprobación de suciedad puede parecer simple e incluso ineficiente (lo abordaré más adelante), resulta que es semánticamente correcto todo el tiempo, mientras que los oyentes del cambio tienen muchos casos extraños en las esquinas y necesitan cosas como el seguimiento de dependencias para que sea más correcto semánticamente. El seguimiento de dependencias de KnockoutJS es una característica inteligente para un problema que AngularJS no tiene.

Problemas con los oyentes del cambio:

  • La sintaxis es atroz, ya que los navegadores no lo admiten de forma nativa. Sí, hay proxies, pero no son correctos semánticamente en todos los casos y, por supuesto, no hay proxies en los navegadores antiguos. La conclusión es que la verificación de errores le permite hacer POJO , mientras que KnockoutJS y Backbone.js lo obligan a heredar de sus clases y acceder a sus datos a través de los accesores.
  • Cambiar la coalescencia. Supongamos que tienes una serie de elementos. Digamos que desea agregar elementos a una matriz, ya que está haciendo un bucle para agregar, cada vez que agregue está activando eventos en el cambio, lo que representa la interfaz de usuario. Esto es muy malo para el rendimiento. Lo que quieres es actualizar la interfaz de usuario solo una vez, al final. Los eventos de cambio son demasiado finos.
  • Los oyentes de cambio se activan inmediatamente en un setter, lo cual es un problema, ya que el oyente de cambios puede cambiar aún más los datos, lo que genera más eventos de cambio. Esto es malo ya que en tu pila puedes tener varios eventos de cambio sucediendo a la vez. Supongamos que tiene dos matrices que deben mantenerse sincronizadas por cualquier motivo. Solo puedes agregar a uno u otro, pero cada vez que agregas, disparas un evento de cambio, que ahora tiene una visión inconsistente del mundo. Este es un problema muy similar al bloqueo de subprocesos, que JavaScript evita ya que cada devolución de llamada se ejecuta exclusivamente y hasta su finalización. Los eventos de cambio rompen esto ya que los establecedores pueden tener consecuencias de gran alcance que no son intencionadas y no son obvias, lo que crea el problema del hilo nuevamente. Resulta que lo que usted quiere hacer es retrasar la ejecución del oyente y garantizar que solo un oyente se ejecuta a la vez, por lo tanto, cualquier código puede cambiar los datos, y sabe que ningún otro código se ejecuta mientras lo hace. .

¿Qué pasa con el rendimiento?

Por lo tanto, puede parecer que somos lentos, ya que la comprobación de errores es ineficaz. Aquí es donde debemos ver los números reales en lugar de tener argumentos teóricos, pero primero definamos algunas restricciones.

Los humanos son:

  • Lento : cualquier cosa superior a 50 ms es imperceptible para los humanos y, por lo tanto, puede considerarse como "instantáneo".

  • Limitado : realmente no puede mostrar más de 2000 piezas de información a un humano en una sola página. Cualquier cosa más que eso es una IU realmente mala, y los humanos no pueden procesar esto de todos modos.

Entonces la pregunta real es esta: ¿Cuántas comparaciones puedes hacer en un navegador en 50 ms? Esta es una pregunta difícil de responder ya que muchos factores entran en juego, pero aquí hay un caso de prueba: http://jsperf.com/angularjs-digest/6 que crea 10,000 observadores. En un navegador moderno, esto toma poco menos de 6 ms. En Internet Explorer 8 tarda unos 40 ms. Como puede ver, esto no es un problema, incluso en navegadores lentos en estos días. Hay una advertencia: las comparaciones deben ser simples para ajustarse al límite de tiempo ... Desafortunadamente, es demasiado fácil agregar una comparación lenta en AngularJS, por lo que es fácil crear aplicaciones lentas cuando no sabes lo que haces. estás haciendo. Pero esperamos tener una respuesta al proporcionar un módulo de instrumentación, que le mostrará cuáles son las comparaciones lentas.

Resulta que los videojuegos y las GPU utilizan el método de comprobación de errores, específicamente porque es consistente. Siempre que superen la frecuencia de actualización del monitor (por lo general, 50-60 Hz, o cada 16.6-20 ms), cualquier rendimiento sobre eso es un desperdicio, por lo que es mejor sacar más cosas que obtener FPS más alto.


AngularJs soporta enlace de datos de dos vías .
Significa que puede acceder a la vista de datos -> Controlador y controlador -> Ver

Por ej.

1)

// If $scope have some value in Controller. 
$scope.name = "Peter";

// HTML
<div> {{ name }} </div>

O / P

Peter

Puede enlazar datos en ng-model como: -
2)

<input ng-model="name" />

<div> {{ name }} </div>

Aquí, en el ejemplo anterior, cualquier entrada que proporcione el usuario, será visible en la etiqueta <div>

Si desea vincular la entrada de html al controlador: -
3)

<form name="myForm" ng-submit="registration()">
   <label> Name </lbel>
   <input ng-model="name" />
</form>

Aquí si quiere usar el name entrada en el controlador, entonces,

$scope.name = {};

$scope.registration = function() {
   console.log("You will get the name here ", $scope.name);
};

ng-model une nuestra vista y la representa en la expresión {{ }} .
ng-modelson los datos que se muestran al usuario en la vista y con los que el usuario interactúa.
Así que es fácil unir datos en AngularJs.


Este es mi entendimiento básico. Bien puede estar equivocado!

  1. Los elementos se observan pasando una función (devolviendo lo que se va a ver) al método $watch .
  2. Los cambios a los elementos observados deben realizarse dentro de un bloque de código envuelto por el método $apply .
  3. Al final de $apply se invoca el método $digest que pasa por cada uno de los relojes y comprobaciones para ver si han cambiado desde la última vez que se ejecutó $digest .
  4. Si se encuentran cambios, se invoca de nuevo el compendio hasta que todos los cambios se estabilicen.

En el desarrollo normal, la sintaxis de enlace de datos en el HTML le dice al compilador de AngularJS que cree los relojes para usted y los métodos del controlador ya se ejecutan dentro de $apply . Así que para el desarrollador de aplicaciones todo es transparente.


Explicando con imágenes:

Enlace de datos necesita un mapeo

La referencia en el alcance no es exactamente la referencia en la plantilla. Cuando vincula datos a dos objetos, necesita un tercero que escuche el primero y modifique el otro.

Aquí, cuando modifica el <input> , toca la información-ref3 . Y el clásico mecanismo de enlace de datos cambiará los datos-ref4 . Entonces, ¿cómo se moverán las otras {{data}} expresiones?

Los eventos llevan a $ digest ()

Angular mantiene un valor oldValue y un oldValue newValue de cada enlace. Y después de cada evento Angular , el famoso bucle $digest() verificará la Lista de vigilancia para ver si algo cambió. Estos eventos angulares son ng-click , ng-change , $http completado ... El $digest() repetirá siempre que cualquier valor oldValue difiera del newValue .

En la imagen anterior, notará que los datos-ref1 y data-ref2 han cambiado.

Conclusiones

Es un poco como el huevo y el pollo. Nunca se sabe quién comienza, pero espero que funcione la mayor parte del tiempo como se esperaba.

El otro punto es que puede comprender fácilmente el impacto profundo de un enlace simple en la memoria y la CPU. Esperemos que las computadoras de escritorio sean lo suficientemente gordas para manejar esto. Los teléfonos móviles no son tan fuertes.


Misko ya dio una excelente descripción de cómo funcionan los enlaces de datos, pero me gustaría agregar mi opinión sobre el problema de rendimiento con el enlace de datos.

Como dijo Misko, alrededor de 2000 enlaces son donde empiezas a ver problemas, pero de todos modos no debes tener más de 2000 datos en una página. Esto puede ser cierto, pero no todos los enlaces de datos son visibles para el usuario. Una vez que comience a construir cualquier tipo de widget o cuadrícula de datos con enlace bidireccional, puede llegar fácilmente a 2000 enlaces, sin tener una mala experiencia de usuario.

Considere, por ejemplo, un cuadro combinado donde puede escribir texto para filtrar las opciones disponibles. Este tipo de control podría tener ~ 150 elementos y seguir siendo altamente utilizable. Si tiene alguna característica adicional (por ejemplo, una clase específica en la opción seleccionada actualmente), comienza a obtener de 3 a 5 enlaces por opción. Coloque tres de estos widgets en una página (por ejemplo, uno para seleccionar un país, el otro para seleccionar una ciudad en dicho país y el tercero para seleccionar un hotel) y ya está entre 1000 y 2000 enlaces.

O considere una cuadrícula de datos en una aplicación web corporativa. 50 filas por página no son irrazonables, cada una de las cuales podría tener entre 10 y 20 columnas. Si creas esto con ng-repeticiones, y / o tienes información en algunas celdas que usa algunos enlaces, podrías acercarte a 2000 enlaces solo con esta cuadrícula.

Creo que esto es un gran problema cuando se trabaja con AngularJS, y la única solución que he podido encontrar hasta ahora es construir widgets sin usar enlaces de doble vía, en lugar de usar ngOnce, desregistrar observadores y trucos similares, o construir Directivas que construyen el DOM con jQuery y la manipulación de DOM. Siento que esto derrota el propósito de usar Angular en primer lugar.

Me encantaría escuchar sugerencias sobre otras formas de manejar esto, pero tal vez debería escribir mi propia pregunta. Quería poner esto en un comentario, pero resultó ser demasiado largo para eso ...

TL; DR
El enlace de datos puede causar problemas de rendimiento en páginas complejas.


Obviamente, no hay una verificación periódica de Scope si hay algún cambio en los Objetos adjuntos. No todos los objetos adjuntos al alcance son observados. Alcance prototípicamente mantiene a los vigilantes $$ . Scope solo se itera a través de este $$watchers cuando se llama $digest .

Angular agrega un observador a los observadores de $$ para cada uno de estos

  1. {{expresión}} - En sus plantillas (y en cualquier otro lugar donde haya una expresión) o cuando definamos ng-model.
  2. $ scope. $ watch ('expresión / función') - En su JavaScript solo podemos adjuntar un objeto de alcance para que lo vea angular.

La función $ watch tiene tres parámetros:

  1. La primera es una función de observador que simplemente devuelve el objeto o simplemente podemos agregar una expresión.

  2. La segunda es una función de escucha que se llamará cuando haya un cambio en el objeto. Todas las cosas como los cambios de DOM se implementarán en esta función.

  3. El tercero es un parámetro opcional que toma un booleano. Si es verdadero, el profundo angular observa el objeto y si su falso angular simplemente hace una referencia al objeto. La implementación aproximada de $ watch se ve así

Scope.prototype.$watch = function(watchFn, listenerFn) {
   var watcher = {
       watchFn: watchFn,
       listenerFn: listenerFn || function() { },
       last: initWatchVal  // initWatchVal is typically undefined
   };
   this.$$watchers.push(watcher); // pushing the Watcher Object to Watchers  
};

Hay algo interesante en Angular llamado Digest Cycle. El ciclo $ digest comienza como resultado de una llamada a $ scope. $ Digest (). Supongamos que cambia un modelo de $ scope en una función de controlador a través de la directiva ng-click. En ese caso, AngularJS activa automáticamente un ciclo de $ digest llamando a $ digest (). Además de ng-click, hay otras directivas / servicios incorporados que le permiten cambiar los modelos (por ejemplo, ng-model, $ timeout, etc.) y desencadenar automáticamente un ciclo $ digest. La implementación aproximada de $ digest se ve así.

Scope.prototype.$digest = function() {
      var dirty;
      do {
          dirty = this.$$digestOnce();
      } while (dirty);
}
Scope.prototype.$$digestOnce = function() {
   var self = this;
   var newValue, oldValue, dirty;
   _.forEach(this.$$watchers, function(watcher) {
          newValue = watcher.watchFn(self);
          oldValue = watcher.last;   // It just remembers the last value for dirty checking
          if (newValue !== oldValue) { //Dirty checking of References 
   // For Deep checking the object , code of Value     
   // based checking of Object should be implemented here
             watcher.last = newValue;
             watcher.listenerFn(newValue,
                  (oldValue === initWatchVal ? newValue : oldValue),
                   self);
          dirty = true;
          }
     });
   return dirty;
 };

Si usamos la función setTimeout () de JavaScript para actualizar un modelo de alcance, Angular no tiene forma de saber qué puede cambiar. En este caso, es nuestra responsabilidad llamar a $ apply () manualmente, lo que desencadena un ciclo de $ digest. De manera similar, si tiene una directiva que configura un detector de eventos DOM y cambia algunos modelos dentro de la función del controlador, debe llamar a $ apply () para asegurarse de que los cambios surtan efecto. La gran idea de $ apply es que podemos ejecutar algunos códigos que no son conscientes de Angular, que aún pueden cambiar las cosas en el alcance. Si envolvemos ese código en $ apply, se ocupará de llamar a $ digest (). Implementación aproximada de $ apply ().

Scope.prototype.$apply = function(expr) {
       try {
         return this.$eval(expr); //Evaluating code in the context of Scope
       } finally {
         this.$digest();
       }
};

el enlace de datos:

¿Qué es el enlace de datos?

Cuando el usuario cambia los datos en la vista, se produce una actualización de ese cambio en el modelo de alcance y viceversa.

¿Como es posible?

Respuesta corta: Con la ayuda del ciclo digestivo.

Descripción: Angular js establece el observador en el modelo de alcance, que activa la función de escucha si hay un cambio en el modelo.

$scope.$watch('modelVar' , function(newValue,oldValue){

// Código de actualización de Dom con nuevo valor

});

Entonces, ¿cuándo y cómo se llama la función de observador?

La función de observador se llama como parte del ciclo de digestión.

El ciclo de resumen se llama automáticamente como parte de las directivas / servicios integrados en js angulares como ng-model, ng-bind, $ timeout, ng-click y otros ... que le permiten activar el ciclo de resumen.

Función de ciclo de digestión:

$scope.$digest() -> digest cycle against the current scope.
$scope.$apply() -> digest cycle against the parent scope 

es decir, $rootScope.$apply()

Nota: $ apply () es igual a $ rootScope. $ Digest () esto significa que la comprobación sucia comienza desde la raíz o la parte superior o el alcance principal hasta todos los ámbitos secundarios $ en la aplicación js angular.

Las características anteriores funcionan en los IE de los navegadores para las versiones mencionadas también solo al asegurarse de que su aplicación sea angular js, lo que significa que está utilizando el archivo de script de marco angularjs al que se hace referencia en la etiqueta de script.

Gracias.


Angular.js crea un observador para cada modelo que creamos a la vista. Cada vez que se cambia un modelo, se agrega una clase "ng-dirty" al modelo, por lo que el observador observará todos los modelos que tengan la clase "ng-dirty" y actualizará sus valores en el controlador y viceversa.





data-binding