Domanda Come funziona il binding dei dati in AngularJS?


Come funziona il collegamento dei dati nel AngularJS struttura?

Non ho trovato dettagli tecnici su il 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 trovato che ci sono Osservatori JavaScript questo può 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";

1803
2018-03-13 10:16


origine


risposte:


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 $apply() metodo, che è ciò che chiamate quando state passando da un mondo non-AngularJS in un mondo AngularJS, chiamate $digest(). Un digest è semplicemente un vecchio controllo sporco. Funziona su tutti i browser ed è totalmente prevedibile.

Per contrastare il dirty-checking (AngularJS) vs cambiare gli ascoltatori (KnockoutJS e Backbone.jsMentre il controllo sporco può sembrare semplice, e anche inefficiente (lo affronterò più avanti), risulta che è semanticamente corretto tutto il tempo, mentre gli ascoltatori di cambiamenti hanno molti casi angusti e hanno bisogno di cose come il monitoraggio delle dipendenze per fare è 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 controllo sporco 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 - Qualunque cosa più veloce di 50 ms è impercettibile per gli esseri umani e quindi può essere considerato come "istantaneo".

  • Limitato - Non puoi mostrare più di 2000 informazioni su un 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 è un caso di test: http://jsperf.com/angularjs-digest/6 che crea 10.000 osservatori. Su un browser moderno questo richiede poco meno di 6 ms. Sopra 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.


2661
2018-03-13 23:47



Misko ha già fornito un'eccellente descrizione di come funzionano i collegamenti dei dati, ma vorrei aggiungere la mia opinione sul problema delle prestazioni con l'associazione dei dati.

Come ha dichiarato Misko, circa 2000 associazioni sono dove inizi a vedere i problemi, ma non dovresti avere più di 2000 informazioni su una pagina. Questo potrebbe essere vero, ma non tutti i dati vincolanti sono visibili all'utente. Una volta che si inizia a costruire qualsiasi tipo di widget o griglia di dati con rilegatura bidirezionale, è possibile facilmente ha colpito 2000 binding, senza avere un brutto ux.

Si consideri, ad esempio, una casella combinata in cui è possibile digitare del testo per filtrare le opzioni disponibili. Questo tipo di controllo potrebbe contenere circa 150 oggetti ed essere ancora altamente utilizzabile. Se ha alcune funzionalità extra (ad esempio una classe specifica sull'opzione attualmente selezionata) inizi a ottenere 3-5 associazioni per opzione. Metti tre di questi widget su una pagina (ad esempio uno per selezionare un paese, l'altro per selezionare una città in detto paese e il terzo per selezionare un hotel) e sei già tra 1000 e 2000 associazioni.

Oppure considera una griglia di dati in un'applicazione web aziendale. 50 righe per pagina non è irragionevole, ognuna delle quali potrebbe contenere 10-20 colonne. Se lo costruisci con ng-ripetizioni e / o hai informazioni in alcune celle che usano alcuni binding, potresti stare avvicinando 2000 binding con questa griglia da solo.

Trovo che questo sia un enorme problema quando si lavora con AngularJS, e l'unica soluzione che sono riuscito a trovare finora è costruire i widget senza usare l'associazione bidirezionale, usando invece ngOnce, annullando la registrazione di watcher e trucchi simili, o costruendo direttive che costruiscono il DOM con jQuery e Manipolazione DOM. Sento che questo sconfigge lo scopo di utilizzare Angular in primo luogo.

Mi piacerebbe sentire suggerimenti su altri modi per gestirlo, ma forse dovrei scrivere la mia domanda. Volevo mettere questo in un commento, ma si è rivelato troppo lungo per quello ...

TL; DR 
L'associazione dati può causare problemi di prestazioni su pagine complesse.


308
2017-08-22 13:28



Con il controllo sporco del $scope oggetto

Angolare mantiene un semplice array di osservatori nel $scope oggetti. Se ispezionate qualsiasi $scope troverai 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 attribute nome 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 marcherà $scope 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 sopra $scope.

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

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

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

Il $digest il ciclo 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 digestione è a traversata in profondità di $scope e tutti i suoi figli. Per ciascuno $scope  object, iteriamo sopra il suo $$watchers  array e valutare 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 $scopeinnescare un AJAX  request, 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 un osservatore è innescato, il $scope è sporco

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

Le funzioni di Watcher possono cambiare altri attributi su $scope o su un genitore $scope. Se uno $watcher la funzione è stata attivata, non possiamo garantire che l'altra nostra $scopes sono ancora puliti, quindi eseguiamo di nuovo l'intero ciclo di digestione.

Ciò è dovuto al fatto che AngularJS ha un'associazione a due vie, pertanto è possibile eseguire il backup dei dati $scopealbero. Possiamo cambiare un valore su un valore più alto $scope che è già stato digerito Forse cambiamo un valore sul $rootScope.

Se la $digest è sporco, eseguiamo l'intero $digest ricominciare

Passiamo continuamente attraverso il $digest cicla fino a quando il ciclo di digestazione non viene pulito (tutto $watch le espressioni 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 controllerà ogni singolo watcher in $scope gerarchia 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 tu ng-repeat oltre un grande JSON  array per esempio. È 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 osservatori nel tuo $scope albero. Un modo semplice per farlo è con un tempo 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 sottoposto a rendering e caricato i dati $scope.

Questo è particolarmente importante quando si ha un ng-repeat con molti oggetti

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

142
2018-06-02 12:31



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

  1. Gli oggetti sono guardati passando una funzione (restituendo la cosa essere guardato) al $watch metodo.
  2. Le modifiche agli articoli guardati devono essere effettuate all'interno di un blocco di codice avvolto dal $apply metodo.
  3. Alla fine di $apply il $digest viene invocato il metodo che va attraverso ciascuno degli orologi e controlli per vedere se sono cambiati da allora l'ultima volta il $digest corse.
  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 $apply già. Quindi per lo sviluppatore dell'applicazione è tutto trasparente.


77
2018-03-13 21:01



Mi sono chiesto questo per un po '. Senza setter come fa AngularJS avviso modifiche al $scope oggetto? Li sta sondando?

Quello che fa in realtà è questo: qualsiasi luogo "normale" che hai modificato il modello è stato già chiamato dalle viscere di AngularJS, quindi chiama automaticamente $apply per te dopo che il tuo codice è stato eseguito. Supponiamo che il controller abbia un metodo a cui è collegato ng-click su qualche elemento Perché AngularJS collega la chiamata di quel metodo insieme per te, ha la possibilità di fare un $apply nel posto appropriato. Allo stesso modo, per le espressioni che appaiono proprio nelle viste, queste vengono eseguite da AngularJS così fa il $apply.

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


57
2017-09-03 17:45



Spiegando con le immagini:

Data-Binding necessita di una mappatura

Il riferimento nell'ambito non è esattamente il riferimento nel modello. Quando si collegano i dati a due oggetti, è necessario un terzo che ascolti il ​​primo e modifichi l'altro.

enter image description here

Qui, quando modifichi il <input>, tocchi il Dati-REF3. E il classico mecanismo di data-data cambierà Dati-RIF4. Quindi, come l'altro {{data}} le espressioni si sposteranno?

Gli eventi portano a $ digest ()

enter image description here

Angolare mantiene a oldValue e newValue di ogni legame E dopo ogni Evento angolare, il famoso $digest() loop controllerà WatchList per vedere se qualcosa è cambiato. Queste Eventi angolari siamo ng-click, ng-change, $http completato ... $digest() loop fino a qualsiasi oldValue differisce dal newValue.

Nell'immagine precedente, si noterà che data-ref1 e data-ref2 sono cambiati.

conclusioni

È un po 'come l'uovo e il pollo. Non si sa mai chi inizia, ma si spera che funzioni la maggior parte del tempo come previsto.

L'altro punto è che puoi capire facilmente l'impatto profondo di un semplice legame sulla memoria e sulla CPU. Spero che i desktop siano abbastanza grassi da gestire questo problema. I telefoni cellulari non sono così potenti.


29
2018-05-20 13:33



Ovviamente non vi è alcun controllo periodico di Scope se c'è qualche cambiamento negli Oggetti ad esso allegati. Non tutti gli oggetti collegati all'ambito vengono guardati. L'ambito mantiene prototipicamente a $$ osservatori . Scope solo itera attraverso questo $$watchers quando $digest è chiamato .

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.

$ orologio la funzione 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 JavaScript setTimeout () funzione per aggiornare un modello di oscilloscopio, 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();
       }
};

19
2018-05-22 18:18



AngularJS gestisce il meccanismo di associazione dei dati con l'aiuto di tre potenti funzioni: $ Watch (),$ Digest ()e $ Applicare (). 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: espressione, listener e oggetto di uguaglianza,   dove listener e object di uguaglianza sono parametri opzionali.

$ Digest () -

Questa funzione scorre tutti gli orologi nell'oggetto $ scope,   e i suoi oggetti child $ scope
     (se ne ha). Quando $ digest () itera   sopra gli orologi, controlla se il valore dell'espressione ha   cambiato. Se il valore è cambiato, AngularJS chiama l'ascoltatore con   nuovo valore e vecchio valore. Viene chiamata la funzione $ digest ()   ogni volta che AngularJS pensa che sia necessario. Ad esempio, dopo un pulsante   fare clic su o dopo una chiamata AJAX. Potresti avere alcuni casi in cui AngularJS   non chiama la funzione $ digest () per te. In tal caso devi   chiamalo tu stesso

$ Applicare () -

Angular auto magicamente aggiorna solo quelle modifiche del modello che sono   all'interno del contesto di AngularJS. Quando cambi in qualsiasi modello al di fuori di   il contesto angolare (come gli eventi DOM del browser, setTimeout, XHR o terzo   librerie di partito), quindi è necessario informare Angular dei cambiamenti di   chiamando $ apply () manualmente. Quando termina la chiamata alla funzione $ apply ()   AngularJS chiama internamente $ digest (), quindi tutti i binding di dati lo sono   aggiornato.


12
2018-05-16 15:05



È successo che avevo bisogno di collegare un modello di dati di una persona con un modulo, quello che ho fatto è stato una mappatura diretta dei dati con il modulo.

Ad esempio se il modello ha qualcosa di simile:

$scope.model.people.name

L'input di controllo del modulo:

<input type="text" name="namePeople" model="model.people.name">

In questo modo se modifichi il valore del controller dell'oggetto, questo verrà riflesso automaticamente nella vista.

Un esempio in cui ho passato il modello è aggiornato dai dati del server quando richiedi un codice postale e un codice postale basati sui carichi scritti, un elenco di colonie e città associate a quella vista e, per impostazione predefinita, imposta il primo valore con l'utente. E questo ho funzionato molto bene, cosa succede, è quello angularJS a volte occorrono alcuni secondi per aggiornare il modello, per fare ciò è possibile inserire uno spinner durante la visualizzazione dei dati.


7
2017-09-18 05:57



  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.


5
2018-06-17 19:28