javascript usa Come funziona il binding dei dati in AngularJS?




modulo angular (12)

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 affermato da 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 binding bidirezionale, è possibile raggiungere facilmente i binding di 2000, senza avere una UX errata.

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à nel suddetto 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 grosso problema quando si lavora con AngularJS, e l'unica soluzione che sono riuscito a trovare finora è quella di costruire i widget senza usare l'associazione bidirezionale, invece di usare ngOnce, deregistrare gli osservatori e trucchi simili, o costruire direttive che costruiscono il DOM con manipolazione jQuery e 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.

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

AngularJs supporta l'associazione dati bidirezionale .
Significa che puoi accedere ai dati Visualizza -> Controller e controller -> Visualizza

Per es.

1)

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

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

OPERAZIONE

Peter

È possibile associare i dati in ng-model Come: -
2)

<input ng-model="name" />

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

Qui nell'esempio sopra, qualunque sia l'input che l'utente darà, sarà visibile nel tag <div> .

Se vuoi associare input da html a controller: -
3)

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

Qui se si desidera utilizzare il name input nel controller, quindi,

$scope.name = {};

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

ng-model lega la nostra vista e la rende espressione {{ }} .
ng-modelè il dato che viene mostrato all'utente nella vista e con cui l'utente interagisce.
Quindi è facile associare i dati in AngularJs.


È 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 ha funzionato molto bene, cosa succede, è che angularJS volte impiega qualche secondo per aggiornare il modello, per fare questo puoi mettere uno spinner mentre mostri i dati.


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.


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.


  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 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.


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.

Qui, quando modifichi <input> , tocchi il data-ref3 . E il classico mecanismo di data-data cambierà i dati-ref4 . Quindi, come si muoveranno le altre {{data}} espressioni?

Gli eventi portano a $ digest ()

Angolare mantiene un valore oldValue e newValue di ogni legame. E dopo ogni evento Angular , il famoso ciclo $digest() controllerà WatchList per vedere se qualcosa è cambiato. Questi eventi angolari sono ng-click , ng-change , $http completato ... Il $digest() verrà oldValue fino a quando qualsiasi valore 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.


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




data-binding