javascript - usa - Come funziona il binding dei dati in AngularJS?




modulo angular (10)

Come funziona il binding dei dati nel framework AngularJS ?

Non ho trovato dettagli tecnici sul loro sito . È più o meno chiaro come funziona quando i dati vengono propagati dalla vista al modello. Ma in che modo AngularJS tiene traccia dei cambiamenti delle proprietà del modello senza setter e getter?

Ho scoperto che ci sono osservatori JavaScript che potrebbero fare questo lavoro. Ma non sono supportati in Internet Explorer 6 e Internet Explorer 7 . Quindi, come fa AngularJS a sapere che ho modificato per esempio quanto segue e riflesso questo cambiamento su una vista?

myobject.myproperty="new value";

Controllando in modo sporco l'oggetto $scope

Angular mantiene una semplice array di watcher negli oggetti $scope . Se controlli qualsiasi $scope scoprirai che contiene un array chiamato $$watchers .

Ogni osservatore è un object che contiene tra le altre cose

  1. Un'espressione che l'osservatore sta monitorando. Questo potrebbe essere solo un nome di attribute o qualcosa di più complicato.
  2. Un ultimo valore noto dell'espressione. Questo può essere verificato rispetto all'attuale valore calcolato dell'espressione. Se i valori differiscono, l'osservatore attiverà la funzione e contrassegnerà $scope come sporco.
  3. Una funzione che verrà eseguita se l'osservatore è sporco.

Come sono definiti gli osservatori

Esistono molti modi diversi per definire un osservatore in AngularJS.

  • Puoi esplicitamente $watch un attribute su $scope .

    $scope.$watch('person.username', validateUnique);
    
  • Puoi inserire un'interpolazione {{}} nel tuo modello (un osservatore verrà creato per te sull'attuale $scope ).

    <p>username: {{person.username}}</p>
    
  • Puoi chiedere a una direttiva come ng-model di definire l'osservatore per te.

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

Il ciclo $digest controlla tutti gli osservatori contro il loro ultimo valore

Quando interagiamo con AngularJS attraverso i normali canali (ng-model, ng-repeat, ecc), un ciclo di digest verrà attivato dalla direttiva.

Un ciclo di digest è una traversata in profondità di $scope e tutti i suoi figli . Per ogni object $scope , iteriamo sul suo array $$watchers e valutiamo tutte le espressioni. Se il nuovo valore di espressione è diverso dall'ultimo valore noto, viene chiamata la funzione del watcher. Questa funzione potrebbe ricompilare parte del DOM, ricalcolare un valore su $scope , attivare una request AJAX , qualsiasi cosa tu abbia bisogno di fare.

Ogni ambito viene attraversato e ogni espressione di controllo viene valutata e controllata rispetto all'ultimo valore.

Se viene attivato un osservatore, $scope è sporco

Se viene attivato un osservatore, l'app sa che qualcosa è cambiato e $scope è contrassegnato come sporco.

Le funzioni di watcher possono modificare altri attributi su $scope o su un $scope . Se è stata attivata una funzione $watcher , non possiamo garantire che gli altri $scope siano ancora puliti, quindi eseguiamo nuovamente l'intero ciclo di digest.

Ciò è dovuto al fatto che AngularJS ha un'associazione a due vie, quindi i dati possono essere passati di nuovo nella struttura $scope . Possiamo cambiare un valore su un $scope superiore che è già stato digerito. Forse cambiamo un valore su $rootScope .

Se $digest è sporco, eseguiamo nuovamente l'intero ciclo $digest

Continuiamo a scorrere il ciclo $digest fino a quando il ciclo di digest non risulterà pulito (tutte le espressioni di $watch hanno lo stesso valore che avevano nel ciclo precedente) o raggiungiamo il limite di digest. Per impostazione predefinita, questo limite è impostato su 10.

Se raggiungiamo il limite di digest AngularJS genererà un errore nella console:

10 $digest() iterations reached. Aborting!

Il digest è duro sulla macchina ma facile per lo sviluppatore

Come puoi vedere, ogni volta che qualcosa cambia in un'app AngularJS, AngularJS controlla ogni singolo watcher nella gerarchia $scope per vedere come rispondere. Per uno sviluppatore questo è un enorme vantaggio in termini di produttività, poiché ora è necessario scrivere quasi nessun codice di cablaggio, AngularJS noterà solo se un valore è cambiato e rende il resto dell'app coerente con la modifica.

Dal punto di vista della macchina, tuttavia, questo è estremamente inefficiente e rallenterà la nostra app se creiamo troppi osservatori. Misko ha citato una cifra di circa 4000 osservatori prima che la tua app si senta in ritardo sui browser più vecchi.

Questo limite è facile da raggiungere se, ad esempio, si ng-repeat su un array JSON grandi dimensioni. È possibile mitigare questo utilizzando funzionalità come l'associazione in una sola volta per compilare un modello senza creare osservatori.

Come evitare di creare troppi osservatori

Ogni volta che l'utente interagisce con la tua app, ogni singolo osservatore nella tua app verrà valutato almeno una volta. Gran parte dell'ottimizzazione di un'app AngularJS sta riducendo il numero di watcher nella struttura $scope . Un modo semplice per farlo è con una volta vincolante .

Se hai dati che cambieranno raramente, puoi associarli una sola volta usando la sintassi ::, in questo modo:

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

o

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

L'associazione verrà attivata solo quando il modello contenente viene reso e i dati caricati in $scope .

Questo è particolarmente importante quando si ha una ng-repeat con molti articoli.

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

  1. L'associazione dati unidirezionale è un approccio in cui un valore viene preso dal modello dati e inserito in un elemento HTML. Non c'è modo di aggiornare il modello dalla vista. È usato nei sistemi di template classici. Questi sistemi collegano i dati in un'unica direzione.

  2. L'associazione dei dati nelle app angolari è la sincronizzazione automatica dei dati tra il modello e i componenti di visualizzazione.

L'associazione dati consente di trattare il modello come singola fonte di verità nella propria applicazione. La vista è una proiezione del modello in ogni momento. Se il modello viene modificato, la vista riflette la modifica e viceversa.


AngularJS gestisce il meccanismo di associazione dei dati con l'aiuto di tre potenti funzioni: $watch() , $digest() e $apply() . Il più delle volte AngularJS chiamerà $ scope. $ Watch () e $ scope. $ Digest (), ma in alcuni casi potrebbe essere necessario chiamare manualmente queste funzioni per aggiornare con nuovi valori.

$ watch () : -

Questa funzione è utilizzata per osservare i cambiamenti in una variabile su $ scope. Accetta tre parametri: expression, listener e object di uguaglianza, dove listener e object di uguaglianza sono parametri opzionali.

$ digest () -

Questa funzione esegue iterazioni su tutti gli orologi nell'oggetto $ scope e sui relativi oggetti child $ scope
(se ne ha). Quando $ digest () esegue iterazioni sugli orologi, controlla se il valore dell'espressione è cambiato. Se il valore è cambiato, AngularJS chiama il listener con nuovo valore e vecchio valore. La funzione $ digest () viene chiamata ogni volta che AngularJS pensa che sia necessario. Ad esempio, dopo un clic del pulsante o dopo una chiamata AJAX. Potresti avere alcuni casi in cui AngularJS non chiama la funzione $ digest () per te. In tal caso devi chiamarlo da solo.

$ apply () -

Angular esegue automaticamente l'aggiornamento automatico delle modifiche del modello che si trovano all'interno del contesto di AngularJS. Quando cambi in qualsiasi modello al di fuori del contesto Angolare (come gli eventi DOM del browser, setTimeout, XHR o librerie di terze parti), devi informare Angular delle modifiche chiamando $ apply () manualmente. Quando la chiamata alla funzione $ apply () termina AngularJS chiama $ digest () internamente, quindi tutte le associazioni di dati vengono aggiornate.


AngularJS memorizza il valore e lo confronta con un valore precedente. Questo è un semplice controllo sporco. Se c'è un cambiamento nel valore, allora spara l'evento change.

Il metodo $apply() , che è quello che chiami quando stai passando da un mondo non-AngularJS in un mondo AngularJS, chiama $digest() . Un digest è semplicemente un vecchio controllo sporco. Funziona su tutti i browser ed è totalmente prevedibile.

Per contrastare il dirty-checking (AngularJS) e cambiare i listener ( KnockoutJS e Backbone.js ): mentre il dirty-checking può sembrare semplice, e persino inefficiente (lo affronterò più avanti), risulta che è semanticamente corretto tutto il tempo, mentre i cambia ascoltatori hanno molti casi angusti e hanno bisogno di cose come il rilevamento delle dipendenze per renderlo più semanticamente corretto. Il rilevamento delle dipendenze KnockoutJS è una funzionalità intelligente per un problema che AngularJS non ha.

Problemi con gli ascoltatori di cambiamenti:

  • La sintassi è atroce, poiché i browser non la supportano in modo nativo. Sì, ci sono proxy, ma non sono semanticamente corretti in tutti i casi, e ovviamente non ci sono proxy sui vecchi browser. La linea di fondo è che il dirty-checking ti permette di fare POJO , mentre KnockoutJS e Backbone.js ti costringono ad ereditare dalle loro classi e ad accedere ai tuoi dati tramite gli accessor.
  • Cambia coalescenza. Supponi di avere una serie di elementi. Supponiamo che tu voglia aggiungere elementi in un array, mentre esegui il ciclo per aggiungere, ogni volta che aggiungi attivi eventi in modifica, che sta rendendo l'interfaccia utente. Questo è molto negativo per le prestazioni. Quello che vuoi è aggiornare l'interfaccia utente solo una volta, alla fine. Gli eventi di modifica sono troppo chiari.
  • Cambiare gli ascoltatori sparare immediatamente su un setter, che è un problema, dal momento che il listener di modifiche può ulteriormente modificare i dati, il che genera più eventi di cambiamento. Questo è un problema poiché sul tuo stack potresti avere diversi eventi di cambiamento che accadono contemporaneamente. Supponiamo di avere due array che devono essere tenuti sincronizzati per qualsiasi motivo. È possibile solo aggiungere a uno o l'altro, ma ogni volta che si aggiunge si attiva un evento di modifica, che ora ha una visione incoerente del mondo. Questo è un problema molto simile al thread lock, che JavaScript evita dal momento che ogni callback viene eseguito esclusivamente e fino al completamento. Gli eventi di cambiamento rompono questo dato che i setter possono avere conseguenze di vasta portata che non sono intese e non ovvie, il che crea di nuovo il problema del filo. Si scopre che ciò che si vuole fare è ritardare l'esecuzione del listener e garantire che venga eseguito un solo listener alla volta, quindi qualsiasi codice è libero di cambiare i dati e sa che nessun altro codice viene eseguito mentre lo fa .

Che dire delle prestazioni?

Quindi può sembrare che siamo lenti, dal momento che il controllo sporco è inefficiente. Qui è dove dobbiamo guardare i numeri reali piuttosto che avere solo argomenti teorici, ma prima definiamo alcuni vincoli.

Gli umani sono:

  • Lento - Qualsiasi cosa più veloce di 50 ms è impercettibile per gli esseri umani e quindi può essere considerata come "istantanea".

  • Limitato - Non puoi mostrare più di circa 2000 informazioni su un essere umano su una singola pagina. Qualunque cosa in più è un'interfaccia utente davvero brutta, e gli umani non possono elaborarlo comunque.

Quindi la vera domanda è questa: quanti confronti puoi fare su un browser in 50 ms? Questa è una domanda difficile a cui rispondere quando entrano in gioco molti fattori, ma qui c'è un caso di test: http://jsperf.com/angularjs-digest/6 che crea 10.000 watcher. Su un browser moderno questo richiede poco meno di 6 ms. Su Internet Explorer 8 ci vogliono circa 40 ms. Come puoi vedere, questo non è un problema nemmeno con i browser lenti in questi giorni. C'è un avvertimento: i confronti devono essere semplici per rientrare nel limite di tempo ... Sfortunatamente è troppo facile aggiungere un confronto lento in AngularJS, quindi è facile creare applicazioni lente quando non si sa cosa sta facendo. Ma speriamo di avere una risposta fornendo un modulo di strumentazione, che ti mostri quali sono i confronti lenti.

Risulta che i videogiochi e le GPU utilizzano l'approccio di controllo sporco, in particolare perché è coerente. Fintanto che superano la frequenza di aggiornamento del monitor (in genere 50-60 Hz, o ogni 16.6-20 ms), qualsiasi prestazione su quella è una perdita, quindi è meglio disegnare più cose, piuttosto che ottenere un FPS più alto.


Ecco un esempio di associazione dati con AngularJS, utilizzando un campo di input. Te lo spiegherò dopo

Codice HTML

<div ng-app="myApp" ng-controller="myCtrl" class="formInput">
     <input type="text" ng-model="watchInput" Placeholder="type something"/>
     <p>{{watchInput}}</p> 
</div>

Codice AngularJS

myApp = angular.module ("myApp", []);
myApp.controller("myCtrl", ["$scope", function($scope){
  //Your Controller code goes here
}]);

Come puoi vedere nell'esempio sopra, AngularJS usa ng-model per ascoltare e vedere cosa succede sugli elementi HTML, specialmente sui campi di input . Quando succede qualcosa, fai qualcosa. Nel nostro caso, ng-model è legato alla nostra vista, usando la notazione dei baffi {{}} . Tutto ciò che viene digitato all'interno del campo di input viene immediatamente visualizzato sullo schermo. E questa è la bellezza del binding dei dati, utilizzando AngularJS nella sua forma più semplice.

Spero che questo ti aiuti.

Vedi un esempio funzionante qui su Codepen


Mi sono chiesto questo per un po '. Senza setter, come fa AngularJS notare le modifiche all'oggetto $scope ? Li sta sondando?

Ciò che effettivamente fa è questo: qualsiasi luogo "normale" che hai modificato il modello è già stato chiamato dal budello di AngularJS , quindi chiama automaticamente $apply dopo che il codice è stato eseguito. Supponiamo che il controller abbia un metodo collegato a ng-click su qualche elemento. Poiché AngularJS per te la chiamata di quel metodo, ha la possibilità di fare $apply nella posizione appropriata. Allo stesso modo, per le espressioni che appaiono direttamente nelle viste, queste vengono eseguite da AngularJS quindi $apply .

Quando la documentazione parla di dover chiamare $apply manualmente per il codice al di fuori di AngularJS , si tratta di codice che, quando eseguito, non deriva da AngularJS stesso nello stack di chiamate.


Ovviamente non vi è alcun controllo periodico su Scope se vi sono cambiamenti negli Oggetti ad esso associati. Non tutti gli oggetti collegati all'ambito vengono guardati. L'ambito mantiene prototipicamente un osservatore di $$ . Scope limita a scorrere gli $$watchers quando viene chiamato $digest .

Angular aggiunge un osservatore agli osservatori $$ per ognuno di questi

  1. {{espressione}} - Nei tuoi modelli (e in qualsiasi altro luogo in cui è presente un'espressione) o quando definiamo ng-model.
  2. $ scope. $ watch ('expression / function') - Nel tuo JavaScript possiamo solo collegare un oggetto scope per il controllo angolare.

La funzione $ watch accetta tre parametri:

  1. Il primo è una funzione di watcher che restituisce appena l'oggetto o possiamo semplicemente aggiungere un'espressione.

  2. Il secondo è una funzione di ascolto che verrà chiamata quando c'è un cambiamento nell'oggetto. Tutte le cose come le modifiche DOM verranno implementate in questa funzione.

  3. Il terzo è un parametro opzionale che accetta un valore booleano. Se il suo vero, profondo angolare guarda l'oggetto e se il suo falso Angolare fa semplicemente un riferimento guardando sull'oggetto. L'implementazione approssimativa di $ watch appare così

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  
};

C'è una cosa interessante in Angular chiamato Digest Cycle. Il ciclo $ digest inizia come risultato di una chiamata a $ scope. $ Digest (). Si supponga di modificare un modello $ scope in una funzione di gestione tramite la direttiva ng-click. In questo caso, AngularJS attiva automaticamente un ciclo $ digest chiamando $ digest (). Oltre a ng-click, ci sono molte altre direttive / servizi incorporati che consentono di modificare i modelli (ad esempio ng-model, $ timeout, ecc.) e attiva automaticamente un ciclo $ digest. L'implementazione approssimativa di $ digest appare così.

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;
 };

Se usiamo la funzione setTimeout () di JavaScript per aggiornare un modello di scope, Angular non ha modo di sapere cosa potresti cambiare. In questo caso è nostra responsabilità chiamare $ apply () manualmente, che attiva un ciclo $ digest. Allo stesso modo, se si dispone di una direttiva che imposta un listener di eventi DOM e modifica alcuni modelli all'interno della funzione di gestione, è necessario chiamare $ apply () per garantire che le modifiche abbiano effetto. La grande idea di $ apply è che possiamo eseguire un codice che non è a conoscenza di Angular, che il codice potrebbe ancora cambiare le cose sull'ambito. Se avvolgiamo quel codice in $ apply, si prenderà cura di chiamare $ digest (). Implementazione approssimativa di $ apply ().

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

Questa è la mia comprensione di base. Potrebbe essere sbagliato!

  1. Gli oggetti sono guardati passando una funzione (restituendo la cosa da guardare) al metodo $watch .
  2. Le modifiche agli articoli guardati devono essere effettuate all'interno di un blocco di codice incluso nel metodo $apply .
  3. Alla fine del $apply il metodo $digest invocato che passa attraverso ciascuno degli orologi e controlla se sono cambiati dall'ultima volta che è stato eseguito il $digest .
  4. Se vengono trovate modifiche, il digest viene richiamato di nuovo fino a quando tutte le modifiche non si stabilizzano.

Nello sviluppo normale, la sintassi di associazione dei dati nell'HTML dice al compilatore AngularJS di creare gli orologi per voi ei metodi del controller vengono eseguiti all'interno di $apply già. Quindi per lo sviluppatore dell'applicazione è tutto trasparente.


associazione dati:

Che cos'è il collegamento dei dati?

Ogni volta che l'utente modifica i dati nella vista, si verifica un aggiornamento di tale modifica nel modello di ambito e viceversa.

Come è possibile?

Risposta breve: con l'aiuto del ciclo di digestione.

Descrizione: js angolare imposta l'osservatore sul modello di ambito, che attiva la funzione listener se si verifica un cambiamento nel modello.

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

// Codice di aggiornamento Dom con nuovo valore

});

Quindi, quando e come viene chiamata la funzione di osservatore?

La funzione Watcher è chiamata come parte del ciclo di digestione.

Il ciclo di digest è chiamato automaticamente attivato come parte di js angolari incorporati in direttive / servizi come ng-model, ng-bind, $ timeout, ng-click e altri .. che consentono di attivare il ciclo di digest.

Funzione del ciclo di digestione:

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

cioè $rootScope.$apply()

Nota: $ apply () è uguale a $ rootScope. $ Digest () significa che il controllo dirty viene avviato direttamente dalla radice o dalla parte superiore o dall'ambito padre in basso a tutti gli ambiti $ child nell'applicazione js angolare.

Le funzionalità di cui sopra funzionano nei browser IE per le versioni menzionate anche solo assicurandosi che l'applicazione sia angolare js, il che significa che si sta utilizzando il file di script framework angularjs a cui si fa riferimento nel tag script.

Grazie.


Angular.js crea un osservatore per ogni modello che creiamo in vista. Ogni volta che un modello viene modificato, una classe "ng-dirty" viene aggiunta al modello, quindi l'osservatore osserverà tutti i modelli che hanno la classe "ng-dirty" e aggiornerà i loro valori nel controller e viceversa.







data-binding