javascript - tutorial - Wie funktioniert die Datenbindung in AngularJS?




javascript data binding (9)

Durch das Überprüfen des $scope Objekts

Angular unterhält ein einfaches array von Beobachtern in den $scope Objekten. Wenn Sie einen $scope Sie feststellen, dass er ein array namens $$watchers .

Jeder Beobachter ist ein object , das unter anderem enthält

  1. Ein Ausdruck, den der Beobachter überwacht. Dies könnte nur ein attribute oder etwas komplizierter sein.
  2. Ein letzter bekannter Wert des Ausdrucks. Dies kann mit dem aktuell berechneten Wert des Ausdrucks verglichen werden. Wenn sich die Werte unterscheiden, löst der Beobachter die Funktion aus und markiert das $scope als schmutzig.
  3. Eine Funktion, die ausgeführt wird, wenn der Beobachter verschmutzt ist.

Wie Beobachter definiert sind

Es gibt viele verschiedene Möglichkeiten, einen Angreifer in AngularJS zu definieren.

  • Sie können explizit $watch ein attribute in $scope .

    $scope.$watch('person.username', validateUnique);
    
  • Sie können eine {{}} Interpolation in Ihre Vorlage einfügen (ein Beobachter wird für Sie auf dem aktuellen $scope ).

    <p>username: {{person.username}}</p>
    
  • Sie können eine Anweisung wie ng-model anfordern, um den Watcher für Sie zu definieren.

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

Der $digest Zyklus überprüft alle Beobachter auf ihren letzten Wert

Wenn wir mit AngularJS über die normalen Kanäle (ng-Modell, ng-repeat usw.) interagieren, wird durch die Direktive ein Digest-Zyklus ausgelöst.

Ein Digest-Zyklus ist ein Tiefen-First-Traversal von $scope und all seinen Kindern . Für jedes $scope object iterieren wir über sein $$watchers array und werten alle Ausdrücke aus. Wenn sich der neue Ausdruckswert vom letzten bekannten Wert unterscheidet, wird die Watcher-Funktion aufgerufen. Diese Funktion könnte einen Teil des DOM neu kompilieren, einen Wert für $scope berechnen, eine AJAX request auslösen, alles, was Sie tun müssen.

Jeder Bereich wird durchlaufen und jeder Watch-Ausdruck wird ausgewertet und mit dem letzten Wert verglichen.

Wenn ein Watcher ausgelöst wird, ist $scope schmutzig

Wenn ein Watcher ausgelöst wird, weiß die App, dass sich etwas geändert hat, und der $scope wird als "dreckig" markiert.

Überwachungsfunktionen können andere Attribute in $scope oder in einem übergeordneten $scope ändern. Wenn eine $watcher Funktion ausgelöst wurde, können wir nicht garantieren, dass unsere anderen $scope s noch sauber sind, und so führen wir den gesamten Digest-Zyklus erneut aus.

Dies liegt daran, dass AngularJS eine bidirektionale Bindung hat, so dass Daten in der $scope Baumstruktur zurückgegeben werden können. Wir können einen Wert für einen höheren $scope ändern, der bereits verdaut wurde. Vielleicht ändern wir einen Wert für $rootScope .

Wenn der $digest ist, führen wir den gesamten $digest Zyklus erneut aus

Wir durchlaufen den $digest Zyklus kontinuierlich, bis entweder der Digest-Zyklus sauber ist (alle $watch Ausdrücke haben den gleichen Wert wie im vorherigen Zyklus) oder wir erreichen das Digest-Limit. Standardmäßig ist dieses Limit auf 10 festgelegt.

Wenn wir das Digest-Limit erreichen, wird AngularJS einen Fehler in der Konsole auslösen:

10 $digest() iterations reached. Aborting!

Der Digest ist schwer auf der Maschine, aber einfach für den Entwickler

Wie Sie sehen, überprüft AngularJS jedes Mal, wenn sich in einer AngularJS-App etwas ändert, jeden einzelnen Beobachter in der $scope Hierarchie, um zu sehen, wie er reagieren soll. Für einen Entwickler ist das eine enorme Produktivitätssteigerung, da Sie jetzt fast keinen Verdrahtungscode schreiben müssen, AngularJS wird nur bemerken, wenn sich ein Wert geändert hat, und den Rest der App mit der Änderung konsistent machen.

Aus der Perspektive der Maschine ist dies jedoch äußerst ineffizient und wird unsere App verlangsamen, wenn wir zu viele Beobachter erstellen. Misko hat eine Zahl von etwa 4000 Zuschauern angegeben, bevor sich Ihre App in älteren Browsern langsam anfühlt.

Dieses Limit ist leicht zu erreichen, wenn Sie beispielsweise über ein großes JSON array ng-repeat . Sie können dagegen mit Funktionen wie der einmaligen Bindung zum Kompilieren einer Vorlage, ohne dass Beobachter erstellt werden, abmildern.

Wie man vermeidet, zu viele Beobachter zu schaffen

Jedes Mal, wenn Ihr Nutzer mit Ihrer App interagiert, wird jeder einzelne Betrachter in Ihrer App mindestens einmal bewertet. Ein großer Teil der Optimierung einer AngularJS-App besteht darin, die Anzahl der Beobachter in Ihrer $scope Struktur zu reduzieren. Eine einfache Möglichkeit, dies zu tun, ist die einmalige Bindung .

Wenn Sie Daten haben, die sich selten ändern, können Sie sie nur einmal mit der :: -Syntax binden:

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

oder

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

Die Bindung wird nur ausgelöst, wenn die enthaltende Vorlage gerendert und die Daten in $scope geladen werden.

Dies ist besonders wichtig, wenn Sie eine ng-repeat mit vielen Elementen haben.

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

Wie funktioniert die Datenbindung im AngularJS Framework?

Ich habe auf ihrer Website keine technischen Details gefunden. Es ist mehr oder weniger klar, wie es funktioniert, wenn Daten von Ansicht zu Modell weitergegeben werden. Aber wie verfolgt AngularJS Änderungen von Modelleigenschaften ohne Setter und Getter?

Ich habe festgestellt, dass es JavaScript-Beobachter gibt , die diese Arbeit machen können. In Internet Explorer 6 und Internet Explorer 7 sie jedoch nicht unterstützt. Wie weiß AngularJS also, dass ich zum Beispiel Folgendes geändert habe und diese Veränderung auf einen Blick reflektiert habe?

myobject.myproperty="new value";

  1. Die unidirektionale Datenbindung ist ein Ansatz, bei dem ein Wert aus dem Datenmodell entnommen und in ein HTML-Element eingefügt wird. Es gibt keine Möglichkeit, das Modell aus der Sicht zu aktualisieren. Es wird in klassischen Templatesystemen verwendet. Diese Systeme binden Daten nur in einer Richtung.

  2. Datenbindung in angularen Apps ist die automatische Synchronisierung von Daten zwischen den Modell- und Ansichtskomponenten.

Mit der Datenbindung können Sie das Modell in Ihrer Anwendung als Single-Source-of-Truth behandeln. Die Ansicht ist eine Projektion des Modells zu jeder Zeit. Wenn das Modell geändert wird, spiegelt die Ansicht die Änderung wider und umgekehrt.


AngularJS handhabt den Datenbindungsmechanismus mit Hilfe von drei mächtigen Funktionen: $watch() , $digest() und $apply() . Die meiste Zeit wird AngularJS $ scope. $ Watch () und $ scope. $ Digest () aufrufen, aber in einigen Fällen müssen Sie diese Funktionen möglicherweise manuell aufrufen, um sie mit neuen Werten zu aktualisieren.

$ watch () : -

Diese Funktion wird verwendet, um Änderungen in einer Variablen im $ scope zu beobachten. Es akzeptiert drei Parameter: Ausdruck, Listener und Gleichheitsobjekt, wobei Listener und Gleichheitsobjekt optionale Parameter sind.

$ verdauen () -

Diese Funktion durchläuft alle Uhren im $ scope-Objekt und seine untergeordneten $ scope-Objekte
(wenn es welche hat). Wenn $ digest () über die Uhren iteriert, prüft es, ob sich der Wert des Ausdrucks geändert hat. Wenn sich der Wert geändert hat, ruft AngularJS den Listener mit einem neuen Wert und einem alten Wert auf. Die Funktion $ digest () wird aufgerufen, wenn AngularJS dies für notwendig erachtet. Zum Beispiel nach einem Klick auf die Schaltfläche oder nach einem AJAX-Anruf. Möglicherweise haben Sie einige Fälle, in denen AngularJS die Funktion $ digest () nicht für Sie aufruft. In diesem Fall müssen Sie es selbst anrufen.

$ apply () -

Angular aktualisiert automatisch nur die Modelländerungen, die sich im AngularJS-Kontext befinden. Wenn Sie in einem Modell außerhalb des Angular-Kontextes (wie Browser-DOM-Ereignisse, SetTimeout, XHR oder Bibliotheken von Drittanbietern) Änderungen vornehmen, müssen Sie Angular über die Änderungen informieren, indem Sie $ apply () manuell aufrufen. Wenn der Funktionsaufruf $ apply () beendet wird, ruft AngularJS intern $ digest () auf, so dass alle Datenbindungen aktualisiert werden.


AngularJS merkt sich den Wert und vergleicht ihn mit einem vorherigen Wert. Dies ist eine grundsätzliche Dirty-Prüfung. Wenn sich der Wert ändert, wird das Änderungsereignis ausgelöst.

Die $apply() -Methode, die Sie aufrufen, wenn Sie von einer Nicht-AngularJS-Welt in eine AngularJS-Welt übergehen, ruft $digest() . Ein Digest ist einfach nur ein schmutziger Check. Es funktioniert auf allen Browsern und ist völlig vorhersehbar.

Gegenüberstellung von dirty-checking (AngularJS) und change Listeners ( KnockoutJS und Backbone.js ): Während dirty-checking einfach erscheint und sogar ineffizient ist (ich werde später darauf eingehen), stellt sich heraus, dass es semantisch immer korrekt ist. während Änderung Listener haben viele seltsame Ecke Fälle und Dinge wie Abhängigkeiten Tracking benötigen, um es semantisch korrekt zu machen. KnockoutJS-Abhängigkeitsverfolgung ist eine clevere Funktion für ein Problem, das AngularJS nicht hat.

Probleme mit Change-Listenern:

  • Die Syntax ist grauenhaft, da Browser sie nicht nativ unterstützen. Ja, es gibt Proxies, aber sie sind nicht in allen Fällen semantisch korrekt, und natürlich gibt es keine Proxies in alten Browsern. Im Endeffekt können Sie mit der Dirty-Checking-Funktion POJO , während Sie mit KnockoutJS und Backbone.js gezwungen sind, von ihren Klassen zu erben und über Accessoren auf Ihre Daten zuzugreifen.
  • Ändern Sie die Koaleszenz. Angenommen, Sie haben ein Array von Elementen. Angenommen, Sie möchten Elemente zu einem Array hinzufügen, während Sie Schleifen hinzufügen, jedes Mal, wenn Sie hinzufügen, zünden Sie Ereignisse bei Änderung, wodurch die Benutzeroberfläche gerendert wird. Dies ist sehr schlecht für die Leistung. Was Sie wollen, ist die Benutzeroberfläche nur einmal am Ende zu aktualisieren. Die Änderungsereignisse sind zu feinkörnig.
  • Ändern Sie Listener feuern sofort auf einem Setter, was ein Problem ist, da der Change-Listener weiter Daten ändern kann, die mehr Änderungsereignisse auslöst. Dies ist schlecht, da auf Ihrem Stack mehrere Änderungsereignisse gleichzeitig auftreten können. Angenommen, Sie haben zwei Arrays, die aus irgendeinem Grund synchron gehalten werden müssen. Sie können nur das eine oder das andere hinzufügen, aber jedes Mal, wenn Sie hinzufügen, zünden Sie ein Änderungsereignis, das jetzt eine inkonsistente Sicht der Welt hat. Dies ist ein sehr ähnliches Problem mit Thread-Sperren, das JavaScript vermeidet, da jeder Callback ausschließlich ausgeführt und abgeschlossen wird. Änderungsereignisse brechen dies, da Setter weitreichende Konsequenzen haben können, die nicht beabsichtigt und nicht offensichtlich sind, wodurch das Thread-Problem erneut entsteht. Es stellt sich heraus, dass Sie die Ausführung des Listeners verzögern und sicherstellen wollen, dass nur ein Listener gleichzeitig ausgeführt wird. Daher ist jeder Code frei, um Daten zu ändern, und er weiß, dass währenddessen kein anderer Code ausgeführt wird .

Was ist mit Leistung?

Es mag also scheinen, dass wir langsam sind, da unsichere Überprüfung ineffizient ist. Hier müssen wir uns reelle Zahlen ansehen, anstatt nur theoretische Argumente zu haben, aber zunächst einige Einschränkungen definieren.

Menschen sind:

  • Langsam - Alles, was schneller als 50 ms ist, ist für den Menschen nicht wahrnehmbar und kann daher als "sofort" betrachtet werden.

  • Eingeschränkt - Sie können einem Menschen auf einer einzigen Seite nicht mehr als etwa 2000 Informationen anzeigen. Alles andere ist eine sehr schlechte Benutzeroberfläche und Menschen können das sowieso nicht verarbeiten.

Die eigentliche Frage ist also: Wie viele Vergleiche können Sie in 50 ms in einem Browser durchführen? Dies ist eine schwierige Frage zu beantworten, da viele Faktoren ins Spiel kommen, aber hier ist ein Testfall: http://jsperf.com/angularjs-digest/6 der 10.000 Zuschauer erzeugt. In einem modernen Browser dauert das knapp 6 ms. Im Internet Explorer 8 dauert es etwa 40 ms. Wie Sie sehen, ist dies auch in langsamen Browsern kein Problem. Es gibt einen Vorbehalt: Die Vergleiche müssen einfach sein, um in das Zeitlimit zu passen ... Leider ist es viel zu einfach, einen langsamen Vergleich in AngularJS hinzuzufügen, so dass es einfach ist, langsame Anwendungen zu erstellen, wenn Sie nicht wissen, was Sie tun sind dabei. Aber wir hoffen, eine Antwort zu haben, indem wir ein Instrumentierungsmodul zur Verfügung stellen, das Ihnen die langsamen Vergleiche zeigt.

Es stellt sich heraus, dass Videospiele und GPUs die Dirty-Checking-Methode verwenden, insbesondere weil sie konsistent ist. Solange sie die Bildwiederholfrequenz des Monitors überbrücken (normalerweise 50-60 Hz oder alle 16,6-20 ms), ist jede darüber liegende Leistung eine Verschwendung, so dass es besser ist, mehr zu zeichnen, als FPS zu erhöhen.


Das ist mein grundlegendes Verständnis. Es kann durchaus falsch sein!

  1. Elemente werden überwacht, indem eine Funktion (die das zu überwachende Objekt zurückgibt) an die $watch Methode übergeben wird.
  2. Änderungen an beobachteten Elementen müssen innerhalb eines Codeblocks vorgenommen werden, der von der Methode $apply umschlossen wird.
  3. Am Ende von $apply die $digest Methode aufgerufen, die jeden der Watts durchläuft und prüft, ob sie sich seit dem letzten Aufruf von $digest geändert haben.
  4. Wenn irgendwelche Änderungen gefunden werden, wird der Digest erneut aufgerufen, bis sich alle Änderungen stabilisiert haben.

Bei der normalen Entwicklung teilt die Datenbindungssyntax im HTML dem AngularJS-Compiler mit, dass er die Uhren für Sie erstellen soll, und Controller-Methoden werden bereits in $apply . Also für den Anwendungsentwickler ist alles transparent.


Erklären mit Bildern:

Data-Binding benötigt ein Mapping

Die Referenz im Bereich ist nicht genau die Referenz in der Vorlage. Wenn Sie zwei Objekte an Daten binden, benötigen Sie eine dritte, die auf die erste hört und die andere ändert.

Hier, wenn Sie die <input> ändern, berühren Sie die Daten-Ref3 . Und der klassische Datenbindungsmechanismus wird Daten-Ref4 ändern . Wie werden sich die anderen {{data}} Ausdrücke bewegen?

Ereignisse führen zu $ ​​digest ()

Angular behält einen oldValue und einen newValue jeder Bindung bei. Und nach jedem Angular-Ereignis überprüft die berühmte $digest() Schleife die WatchList, um festzustellen, ob sich etwas geändert hat. Diese Angular-Ereignisse sind ng-click , ng-change , $http completed ... Die $digest() -Schleife wird so lange ausgeführt, wie sich irgendein oldValue von dem newValue .

Im vorherigen Bild wird bemerkt, dass sich data-ref1 und data-ref2 geändert haben.

Schlussfolgerungen

Es ist ein bisschen wie das Ei und Hühnchen. Man weiß nie, wer anfängt, aber hoffentlich funktioniert es die meiste Zeit wie erwartet.

Der andere Punkt ist, dass Sie leicht die Auswirkungen einer einfachen Bindung auf den Speicher und die CPU verstehen können. Hoffentlich sind Desktops fett genug, um damit umzugehen. Mobiltelefone sind nicht so stark.


Hier sehen Sie ein Beispiel für die Datenbindung mit AngularJS unter Verwendung eines Eingabefeldes. Ich werde es später erklären

HTML Quelltext

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

AngularJS-Code

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

Wie Sie im obigen Beispiel sehen können, verwendet AngularJS das ng-model , um zu hören und zu beobachten, was in HTML-Elementen passiert, insbesondere in input . Wenn etwas passiert, tu etwas. In unserem Fall ist ng-model mit der Schnurrbartnotation {{}} an unsere Ansicht gebunden. Was auch immer im Eingabefeld eingegeben wird, wird sofort auf dem Bildschirm angezeigt. Und das ist die Schönheit der Datenbindung mit AngularJS in seiner einfachsten Form.

Hoffe das hilft.

Sehen Sie ein Arbeitsbeispiel hier auf Codepen


Ich habe mich das eine Weile selbst gefragt. Ohne Setter wie bemerkt AngularJS Änderungen am $scope Objekt? Befragt es sie?

Was es tatsächlich tut, ist dies: Jeder "normale" Ort, an dem Sie das Modell geändert haben, wurde bereits aus den Eingeweiden von AngularJS aufgerufen, so dass automatisch $apply nach dem Ausführen Ihres Codes für Sie gilt. Angenommen, Ihr Controller verfügt über eine Methode, die mit einem ng-click auf ein Element verbunden ist. Da AngularJS das Aufrufen dieser Methode für Sie zusammenführt, hat es die Möglichkeit, ein $apply an der entsprechenden Stelle $apply . Gleichermaßen werden für Ausdrücke, die direkt in den Ansichten angezeigt werden, diese von AngularJS ausgeführt, so dass das $apply .

Wenn in der Dokumentation davon die Rede ist, dass $apply manuell für Code außerhalb von AngularJS aufgerufen werden muss, handelt es sich um einen Code, der, wenn er ausgeführt wird, nicht von AngularJS selbst im Call-Stack stammt.


Offensichtlich gibt es keine regelmäßige Überprüfung von Scope ob sich die daran angehängten Objekte geändert haben. Nicht alle Objekte, die an den Bereich angehängt sind, werden überwacht. Scope prototypisch überwacht $$ Beobachter . Scope durchläuft nur diese $$watchers wenn $digest aufgerufen wird.

Angular fügt den Beobachtern für jede von ihnen einen Beobachter hinzu

  1. {{expression}} - In Ihren Templates (und wo auch immer es einen Ausdruck gibt) oder wenn wir ng-model definieren.
  2. $ scope. $ watch ('expression / function') - In Ihrem JavaScript können wir einfach ein Oszilloskop-Objekt anhängen, um es zu betrachten.

Die Funktion $ watch nimmt drei Parameter auf:

  1. Die erste ist eine Watcher-Funktion, die einfach das Objekt zurückgibt oder wir können einfach einen Ausdruck hinzufügen.

  2. Die zweite ist eine Listener-Funktion, die aufgerufen wird, wenn sich das Objekt ändert. Alle Dinge wie DOM-Änderungen werden in dieser Funktion implementiert.

  3. Der dritte ist ein optionaler Parameter, der einen booleschen Wert annimmt. Wenn seine wahre, eckige Tiefe das Objekt beobachtet und wenn sein falscher Winkel gerade eine Referenz auf das Objekt beobachtet. Grobe Implementierung von $ watch sieht so aus

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

Es gibt eine interessante Sache in Angular namens Digest Cycle. Der $ Digest-Zyklus beginnt als Ergebnis eines Aufrufs von $ scope. $ Digest (). Angenommen, Sie ändern ein $ -Skalenmodell in einer Handlerfunktion durch die ng-click-Anweisung. In diesem Fall löst AngularJS automatisch einen $ digest-Zyklus aus, indem $ digest () aufgerufen wird. Zusätzlich zu ng-click gibt es noch weitere eingebaute Direktiven / Dienste, mit denen Sie Modelle ändern können (zB ng-model, $ timeout, etc) und automatisch einen $ Digest-Zyklus auslösen. Die grobe Implementierung von $ Digest sieht so aus.

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

Wenn wir die Funktion setTimeout () von JavaScript verwenden, um ein Oszilloskopmodell zu aktualisieren, kann Angular nicht wissen, was Sie ändern könnten. In diesem Fall liegt es in unserer Verantwortung, $ apply () manuell aufzurufen, wodurch ein $ Digest-Zyklus ausgelöst wird. Wenn Sie eine Direktive haben, die einen DOM-Event-Listener einrichtet und einige Modelle innerhalb der Handler-Funktion ändert, müssen Sie $ apply () aufrufen, um sicherzustellen, dass die Änderungen wirksam werden. Die große Idee von $ apply besteht darin, dass wir einen Code ausführen können, der Angular nicht kennt. Dieser Code kann Dinge im Bereich noch ändern. Wenn wir diesen Code in $ apply einbinden, kümmert er sich um den Aufruf von $ digest (). Grobe Implementierung von $ apply ().

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






data-binding