Vraag Hoe werkt databinding in AngularJS?


Hoe werkt databinding in de AngularJS kader?

Ik heb geen technische details gevonden hun site. Het is min of meer duidelijk hoe het werkt wanneer gegevens worden doorgegeven van weergave naar model. Maar hoe houdt AngularJS veranderingen van modeleigenschappen bij zonder setters en getters?

Ik vond dat er zijn JavaScript-kijkers dat kan dit werk doen. Maar ze worden niet ondersteund in Internet Explorer 6 en Internet Explorer 7. Dus hoe weet AngularJS dat ik bijvoorbeeld het volgende heb veranderd en deze verandering in een weergave weerspiegeld heb?

myobject.myproperty="new value";

1803
2018-03-13 10:16


oorsprong


antwoorden:


AngularJS onthoudt de waarde en vergelijkt deze met een vorige waarde. Dit is elementaire vuile controle. Als er een waardewijziging is, wordt de wijzigingsgebeurtenis gestart.

De $apply() methode, dat is wat je noemt wanneer je overgaat van een niet-AngularJS-wereld naar een AngularJS-wereld, oproepen $digest(). Een samenvatting is gewoon oude vieze controle. Het werkt op alle browsers en is volledig voorspelbaar.

Contrapunt van vuile controlerende (AngularJS) vs. change luisteraars (KnockoutJS en Backbone.js): Hoewel dirty-checking eenvoudig lijkt en zelfs inefficiënt (ik zal daar later op ingaan), blijkt het semantisch de hele tijd correct te zijn, terwijl verandering-luisteraars veel rare hoekzaken hebben en dingen als afhankelijkheidscontrole nodig hebben om het is meer semantisch correct. Knockout Het volgen van de afhankelijkheid van JS is een slimme functie voor een probleem dat AngularJS niet heeft.

Problemen met veranderaarsluisteraars:

  • De syntaxis is afschuwelijk, omdat browsers het niet native ondersteunen. Ja, er zijn volmachten, maar ze zijn in alle gevallen niet semantisch correct, en natuurlijk zijn er geen proxies op oude browsers. De bottom line is dat je met dirty-checking kunt doen POJO, terwijl KnockoutJS en Backbone.js je dwingen om te erven van hun klassen, en toegang hebben tot je gegevens via accessors.
  • Verander coalescentie. Stel dat je een hele reeks items hebt. Stel dat u items wilt toevoegen aan een array, terwijl u een lus toevoegt om toe te voegen, elke keer dat u toevoegt, worden gebeurtenissen gebushed op verandering, waardoor de gebruikersinterface wordt gerenderd. Dit is erg slecht voor de prestaties. Wat u wilt, is de UI aan het einde slechts één keer bijwerken. De veranderingsgebeurtenissen zijn te fijnkorrelig.
  • Verander luisteraars schieten direct op een setter, wat een probleem is, omdat de change-listener gegevens verder kan wijzigen, waardoor meer veranderingsgebeurtenissen worden geactiveerd. Dit is slecht, want op je stapel kun je verschillende wijzigingsgebeurtenissen tegelijkertijd laten plaatsvinden. Stel dat u twee arrays hebt die om welke reden dan ook synchroon moeten worden gehouden. U kunt alleen aan de een of de ander toevoegen, maar elke keer dat u toevoegt, wordt een wijzigingsgebeurtenis gestart, die nu een inconsistent beeld van de wereld heeft. Dit is een vergelijkbaar probleem als thread locking, wat JavaScript vermijdt omdat elke callback exclusief en tot voltooiing wordt uitgevoerd. Wijzigingen doorbreken dit omdat setters verstrekkende gevolgen kunnen hebben die niet bedoeld en niet voor de hand liggend zijn, waardoor het draadprobleem helemaal opnieuw ontstaat. Het lijkt erop dat je de uitvoering van de listener vertraagt ​​en garandeert dat slechts één luisteraar tegelijkertijd wordt uitgevoerd. Daarom is elke code vrij om gegevens te wijzigen en weet hij dat er geen andere code wordt uitgevoerd terwijl hij dit doet .

Hoe zit het met de prestaties?

Dus het lijkt misschien dat we traag zijn, omdat vuile controle inefficiënt is. Dit is waar we naar echte getallen moeten kijken in plaats van alleen theoretische argumenten te hebben, maar laten we eerst een aantal beperkingen definiëren.

Mensen zijn:

  • Langzaam - Alles dat sneller is dan 50 ms is onmerkbaar voor mensen en kan daarom als "instant" worden beschouwd.

  • Beperkt - Je kunt niet echt meer dan ongeveer 2000 stukjes informatie aan een mens laten zien op een enkele pagina. Iets meer dan dat is echt een slechte UI, en mensen kunnen dit toch niet verwerken.

Dus de echte vraag is dit: Hoeveel vergelijkingen kun je in 50 ms in een browser uitvoeren? Dit is een moeilijke vraag om te beantwoorden, omdat er veel factoren in het spel komen, maar hier is een testcase: http://jsperf.com/angularjs-digest/6 dat 10.000 toeschouwers maakt. In een moderne browser duurt dit iets minder dan 6 ms. Op Internet Explorer 8 het duurt ongeveer 40 ms. Zoals u kunt zien, is dit tegenwoordig geen probleem, zelfs niet voor langzame browsers. Er is een waarschuwing: de vergelijkingen moeten eenvoudig in de tijdslimiet passen ... Helaas is het veel te gemakkelijk om een ​​langzame vergelijking toe te voegen aan AngularJS, dus het is eenvoudig om langzame applicaties te bouwen als je niet weet wat je bent zijn aan het doen. Maar we hopen een antwoord te krijgen door een instrumentatiemodule te leveren, die u laat zien welke de trage vergelijkingen zijn.

Het blijkt dat videogames en GPU's de aanpak voor vuile controle gebruiken, vooral omdat het consistent is. Zolang ze de vernieuwingsfrequentie van de monitor (meestal 50-60 Hz of elke 16,6-20 ms) overschrijden, is elke prestatie daarboven verspilling, dus je kunt beter meer dingen tekenen dan FPS hoger te krijgen.


2661
2018-03-13 23:47



Misko gaf al een uitstekende beschrijving van hoe de databindingen werken, maar ik zou graag mijn mening willen toevoegen over het prestatieprobleem met de databinding.

Zoals Misko al zei, zijn er rond 2000 bindingen waar je problemen begint te zien, maar je zou toch niet meer dan 2000 stukjes informatie op een pagina moeten hebben. Dit kan waar zijn, maar niet elke databinding is zichtbaar voor de gebruiker. Zodra u begint met het bouwen van elk soort widget of gegevensraster met tweewegsbinding, kunt u dit doen gemakkelijk druk op 2000 bindingen, zonder een slechte ux te hebben.

Overweeg bijvoorbeeld een combobox waarin u tekst kunt typen om de beschikbare opties te filteren. Dit soort controle zou ~ 150 items kunnen hebben en nog steeds zeer bruikbaar zijn. Als het een extra functie heeft (bijvoorbeeld een specifieke klasse op de momenteel geselecteerde optie), krijgt u 3-5 bindingen per optie. Zet drie van deze widgets op een pagina (bijvoorbeeld een om een ​​land te selecteren, de ander om een ​​stad in dat land te selecteren en de derde om een ​​hotel te selecteren) en je bent al tussen 1000 en 2000 bindingen.

Of overweeg een data-grid in een zakelijke webapplicatie. 50 rijen per pagina is niet onredelijk, die elk 10-20 kolommen kunnen hebben. Als je dit met ng-repeats bouwt en / of informatie hebt in sommige cellen die sommige bindingen gebruiken, zou je alleen al 2000-bindingen kunnen benaderen met dit raster.

Ik vind dit een reusachtig probleem bij het werken met AngularJS, en de enige oplossing die ik tot nu toe heb kunnen vinden, is om widgets te bouwen zonder gebruik te maken van bidirectionele binding, in plaats daarvan ngOnce, deregistrerende watchers en soortgelijke trucs te gebruiken, of constructrichtlijnen te maken die de DOM met jQuery en DOM-manipulatie. Ik voel dat dit het doel van het gebruik van Angular in de eerste plaats verslaat.

Ik hoor graag suggesties op andere manieren om hiermee om te gaan, maar misschien moet ik dan mijn eigen vraag stellen. Ik wilde dit in een opmerking plaatsen, maar het bleek veel te lang te duren om dat ...

TL; DR 
De gegevensbinding kan prestatieproblemen veroorzaken op complexe pagina's.


308
2017-08-22 13:28



Door vies het te controleren $scope voorwerp

Angular onderhoudt een eenvoudig array van kijkers in de $scope voorwerpen. Als u een inspecteert $scope je zult merken dat het een bevat array riep $$watchers.

Elke kijker is een object dat bevat onder andere dingen

  1. Een uitdrukking die de bewaker bewaakt. Dit kan gewoon een zijn attribute naam, of iets ingewikkelder.
  2. Een laatst bekende waarde van de expressie. Dit kan worden vergeleken met de huidige berekende waarde van de uitdrukking. Als de waarden verschillen, zal de watcher de functie activeren en de $scope zo vies.
  3. Een functie die wordt uitgevoerd als de kijker vies is.

Hoe kijkers zijn gedefinieerd

Er zijn veel verschillende manieren om een ​​watcher te definiëren in AngularJS.

  • Dat kun je expliciet doen $watch een attribute op $scope.

    $scope.$watch('person.username', validateUnique);
    
  • Je kunt een plaatsen {{}} interpolatie in uw sjabloon (er wordt een watcher voor u gemaakt op basis van de huidige $scope).

    <p>username: {{person.username}}</p>
    
  • U kunt een richtlijn vragen zoals ng-model om de kijker voor jou te definiëren.

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

De $digest cyclus controleert alle watchers op hun laatste waarde

Wanneer we communiceren met AngularJS via de normale kanalen (ng-model, n-herhaling, enz.), Zal een digest-cyclus worden geactiveerd door de richtlijn.

Een digest-cyclus is een diepte-eerste traversal van $scope en al zijn kinderen. Voor elk $scope  object, we itereren over zijn $$watchers  array en evalueer alle uitdrukkingen. Als de nieuwe expressiewaarde anders is dan de laatst bekende waarde, wordt de functie van de watcher aangeroepen. Deze functie kan een deel van de DOM opnieuw compileren, een waarde opnieuw berekenen $scope, trigger een AJAX  request, alles wat je nodig hebt om te doen.

Elke scope wordt doorlopen en elke watch-expressie wordt geëvalueerd en vergeleken met de laatste waarde.

Als een watcher wordt geactiveerd, de $scope is vies

Als een watcher wordt geactiveerd, weet de app dat er iets is veranderd, en de $scope is gemarkeerd als vies.

Watcher-functies kunnen andere kenmerken wijzigen $scope of op een ouder $scope. Als een $watcher functie is geactiveerd, kunnen we niet garanderen dat onze andere $scopes zijn nog steeds schoon, dus we voeren de volledige digestiecyclus opnieuw uit.

Dit komt omdat AngularJS tweewegsbinding heeft, zodat gegevens kunnen worden doorgegeven via de $scopeboom. We kunnen een waarde naar een hoger niveau wijzigen $scope dat is al verteerd. Misschien veranderen we een waarde op de $rootScope.

Als het $digest is vies, we voeren het geheel uit $digest opnieuw fietsen

We lopen continu door de $digest cyclus totdat de digest cyclus schoon is (allemaal $watch expressies hebben dezelfde waarde als die ze hadden in de vorige cyclus), of we bereiken de digest-limiet. Standaard is deze limiet ingesteld op 10.

Als we de digest-limiet bereiken, zal AngularJS een fout in de console veroorzaken:

10 $digest() iterations reached. Aborting!

De samenvatting is moeilijk voor de machine, maar eenvoudig voor de ontwikkelaar

Zoals u kunt zien, zal AngularJS elke keer dat er iets verandert in een AngularJS-app, elke kijker in de app controleren $scope hiërarchie om te zien hoe te reageren. Voor een ontwikkelaar is dit een gigantische productiviteit zegen, aangezien je nu bijna geen bedradingscode hoeft te schrijven, zal AngularJS net opmerken als een waarde is veranderd, en de rest van de app consistent maken met de verandering.

Vanuit het perspectief van de machine is dit echter inefficiënt en vertragen we onze app als we teveel kijkers maken. Misko heeft een cijfer van ongeveer 4000 kijkers geciteerd voordat je app langzaam zal aanvoelen in oudere browsers.

Deze limiet is gemakkelijk te bereiken ng-repeat over een grote JSON  array bijvoorbeeld. U kunt hiertegen verzachten door functies als eenmalige binding te gebruiken om een ​​sjabloon samen te stellen zonder watchers te maken.

Hoe te voorkomen dat te veel kijkers worden gemaakt

Telkens wanneer uw gebruiker met uw app communiceert, wordt elke kijker in uw app ten minste één keer geëvalueerd. Een groot deel van het optimaliseren van een AngularJS-app vermindert het aantal kijkers in uw $scope boom. Een eenvoudige manier om dit te doen is met eenmalige binding.

Als u gegevens hebt die zelden zullen veranderen, kunt u deze maar één keer binden met de :: syntaxis, zoals zo:

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

of

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

De binding wordt alleen geactiveerd wanneer de bevattende sjabloon wordt weergegeven en de gegevens worden geladen $scope.

Dit is vooral belangrijk als je een hebt ng-repeat met veel items.

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

142
2018-06-02 12:31



Dit is mijn basisbegrip. Het is misschien verkeerd!

  1. Items worden bekeken door een functie door te geven (het object teruggeven) bekeken) naar de $watch methode.
  2. Wijzigingen in bekeken items moeten binnen een codeblok worden gemaakt ingepakt door de $apply methode.
  3. Aan het einde van de $apply de $digest methode wordt opgeroepen die gaat door elk van de horloges en controles om te zien of ze sindsdien zijn veranderd vorige keer de $digest liep.
  4. Als er wijzigingen worden gevonden, wordt de samenvatting opnieuw opgeroepen totdat alle wijzigingen zijn gestabiliseerd.

In de normale ontwikkeling vertelt de databinding-syntaxis in de HTML de AngularJS-compiler om de horloges voor u te maken en worden de controlemethoden binnen uitgevoerd $apply nu al. Dus voor de applicatieontwikkelaar is het allemaal transparant.


77
2018-03-13 21:01



Ik vroeg me dit al een tijdje af. Zonder setters hoe werkt AngularJS merk veranderingen op in $scope voorwerp? Vraagt ​​het hen?

Wat het eigenlijk doet is dit: elke "normale" plaats waar je het model aan hebt aangepast, was al geroepen vanuit het lef van AngularJS, dus het roept automatisch $apply voor jou nadat je code is uitgevoerd. Stel dat uw controller een methode heeft waaraan u bent gekoppeld ng-click op een bepaald element. Omdat AngularJS verbindt de roeping van die methode samen voor u, het heeft een kans om een ​​te doen $apply op de juiste plaats. Evenzo worden expressies die recht in de views verschijnen, uitgevoerd door AngularJS dus het doet het $apply.

Wanneer de documentatie spreekt over bellen $apply handmatig voor code buiten AngularJS, het is een code die, wanneer hij wordt uitgevoerd, niet uit stamt AngularJS zelf in de call-stack.


57
2017-09-03 17:45



Uitleggen met afbeeldingen:

Data-binding vereist een mapping

De referentie in het bereik is niet precies de referentie in de sjabloon. Wanneer u twee objecten met gegevens verbindt, hebt u een derde nodig die naar de eerste luistert en de andere wijzigt.

enter image description here

Hier, wanneer u de <input>, raak je de data-REF3. En het klassieke data-bind mecanisme zal veranderen data-ref4. Dus hoe de andere {{data}} uitdrukkingen zullen bewegen?

Gebeurtenissen leiden tot $ digest ()

enter image description here

Angular onderhoudt een oldValue en newValue van elke binding. En na elke Hoekige gebeurtenis, de beroemde $digest() loop controleert de WatchList om te zien of er iets is veranderd. Deze Hoekige gebeurtenissen zijn ng-click, ng-change, $http voltooid ... De $digest() zal lus zolang als om het even welk oldValue verschilt van de newValue.

In de vorige afbeelding zal het opvallen dat data-ref1 en data-ref2 zijn veranderd.

conclusies

Het lijkt een beetje op het ei en de kip. Je weet nooit wie begint, maar hopelijk werkt het meestal zoals verwacht.

Het andere punt is dat je de impact van een eenvoudige binding aan het geheugen en de CPU gemakkelijk kunt begrijpen. Hopelijk zijn desktops dik genoeg om hiermee om te gaan. Mobiele telefoons zijn niet zo sterk.


29
2018-05-20 13:33



Uiteraard is er geen periodieke controle van Scope of er een verandering is in de objecten die eraan zijn gekoppeld. Niet alle objecten die aan het bereik zijn gekoppeld, worden bekeken. Scope behoudt een prototype van een $$ watchers . Scope alleen itereert hierdoor $$watchers wanneer $digest wordt genoemd .

Angular voegt een watcher toe aan de $$ watchers voor elk van deze

  1. {{expression}} - In uw sjablonen (en overal waar een uitdrukking voorkomt) of wanneer we ng-model definiëren.
  2. $ scope. $ watch ('expressie / functie') - In uw JavaScript kunnen we alleen een scope-object koppelen voor hoekig om te bekijken.

$ horloge functie neemt in drie parameters:

  1. De eerste is een watcher-functie die alleen het object retourneert of we kunnen gewoon een expressie toevoegen.

  2. De tweede is een luisterfunctie die wordt aangeroepen wanneer er een verandering in het object is. Alle dingen zoals DOM-wijzigingen worden in deze functie geïmplementeerd.

  3. De derde is een optionele parameter die een boolean bevat. Als het ware, hoekig diep het object bekijkt, en als het false is, doet Angular een referentie naar het object.     Rough Implementation of $ watch ziet er als volgt uit

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

Er is een interessant ding in Angular genaamd Digest Cycle. De $ digest-cyclus start als resultaat van een aanroep naar $ scope. $ Digest (). Stel dat u een $ scope-model in een handler-functie via de ng-click-richtlijn wijzigt. In dat geval start AngularJS automatisch een $ digest-cyclus door $ digest () aan te roepen. Naast ng-click zijn er verschillende andere ingebouwde richtlijnen / services waarmee je van model kunt veranderen (bijv. Ng-model, $ time-out, enz.) en activeer automatisch een $ digest-cyclus. De ruwe implementatie van $ digest ziet er zo uit.

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

Als we JavaScript's gebruiken setTimeout () functie om een ​​scopemodel bij te werken, heeft Angular geen manier om te weten wat u zou kunnen veranderen. In dit geval is het onze verantwoordelijkheid om $ apply () handmatig aan te roepen, waardoor een $ digest-cyclus wordt geactiveerd. Als u een richtlijn hebt die een DOM-gebeurtenislistener instelt en sommige modellen binnen de handlerfunctie wijzigt, moet u $ apply () aanroepen om ervoor te zorgen dat de wijzigingen van kracht worden. Het grote idee van $ is dat we code kunnen uitvoeren die niet op de hoogte is van Angular, maar dat die code nog steeds dingen op de scope kan veranderen. Als we die code in $ toepassen, zorgen we ervoor dat $ digest () wordt aangeroepen. Ruwe implementatie van $ 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 verwerkt databindingsmechanisme met behulp van drie krachtige functies: $ Watch (),$ Digest ()en $ Van toepassing zijn (). Meestal zal AngularJS de $ scope. $ Watch () en $ scope. $ Digest () noemen, maar in sommige gevallen moet u deze functies wellicht handmatig oproepen om bij te werken met nieuwe waarden.

$ Watch () : -

Deze functie wordt gebruikt om veranderingen in een variabele op de $ scope te observeren.   Het accepteert drie parameters: uitdrukking, luisteraar en gelijkheidsobject,   waar luisteraar en gelijkheidsobject optionele parameters zijn.

$ Digest () -

Deze functie doorloopt alle horloges in het $ scope-object,   en de onderliggende $ scope-objecten
     (als het een heeft). Wanneer $ digest () itereert   op de horloges wordt gecontroleerd of de waarde van de expressie klopt   veranderd. Als de waarde is gewijzigd, belt AngularJS de listener aan   nieuwe waarde en oude waarde. De functie $ digest () wordt aangeroepen   wanneer AngularJS denkt dat het nodig is. Bijvoorbeeld na een knop   klik of na een AJAX-oproep. Mogelijk hebt u enkele gevallen waarin AngularJS   roept niet de $ digest () -functie voor u op. In dat geval moet dat   noem het zelf.

$ Van toepassing zijn () -

Angular werkt automatisch op magische wijze alleen die modelwijzigingen bij die dat wel zijn   binnen de AngularJS context. Wanneer je in een ander model buiten je verandert   de hoekige context (zoals DOM-gebeurtenissen in de browser, setTimeout, XHR of een derde   feestbibliotheken), dan moet je Angular informeren over de wijzigingen door   $ apply () handmatig aanroepen. Wanneer de $ apply () -functie aanklopt   AngularJS roept $ digest () intern aan, dus alle databindingen zijn   bijgewerkt.


12
2018-05-16 15:05