Question Comment fonctionne la liaison de données dans AngularJS?


Comment fonctionne la liaison de données dans AngularJS cadre?

Je n'ai pas trouvé de détails techniques sur leur site. Il est plus ou moins clair comment cela fonctionne quand les données sont propagées d'une vue à l'autre. Mais comment AngularJS suit-il les changements de propriétés du modèle sans setters et getters?

J'ai trouvé qu'il y a Observateurs JavaScript cela peut faire ce travail. Mais ils ne sont pas pris en charge dans Internet Explorer 6 et Internet Explorer 7. Alors, comment AngularJS sait-il que j'ai changé par exemple ce qui suit et reflété ce changement sur une vue?

myobject.myproperty="new value";

1803
2018-03-13 10:16


origine


Réponses:


AngularJS mémorise la valeur et la compare à une valeur précédente. C'est une vérification de base. S'il y a un changement de valeur, alors il déclenche l'événement de changement.

le $apply() méthode, qui est ce que vous appelez lorsque vous passez d'un monde non-AngularJS dans un monde AngularJS, appels $digest(). Un résumé est tout simplement vieux sale-vérification. Il fonctionne sur tous les navigateurs et est totalement prévisible.

Contraster les auditeurs corrompus (AngularJS) et les auditeurs de changement (KnockoutJS et Backbone.js): Bien que le "dirty-checking" puisse sembler simple et même inefficace (j'aborderai cela plus tard), il s'avère qu'il est sémantiquement correct tout le temps, alors que les auditeurs de changement ont beaucoup de cas bizarres et ont besoin de choses comme le suivi des dépendances c'est plus sémantiquement correct. Le suivi des dépendances KnockoutJS est une fonction astucieuse pour un problème que AngularJS n'a pas.

Problèmes avec les auditeurs de changement:

  • La syntaxe est atroce, car les navigateurs ne la supportent pas nativement. Oui, il y a des proxies, mais ils ne sont pas sémantiquement corrects dans tous les cas, et bien sûr il n'y a pas de proxy sur les anciens navigateurs. La ligne de fond est que sale-vérification vous permet de faire POJO, alors que KnockoutJS et Backbone.js vous forcent à hériter de leurs classes et accèdent à vos données via des accesseurs.
  • Changer la coalescence. Supposons que vous avez un tableau d'éléments. Supposons que vous souhaitiez ajouter des éléments dans un tableau, comme vous ajoutez des éléments en boucle, chaque fois que vous ajoutez des événements lors du changement, ce qui correspond au rendu de l'interface utilisateur. C'est très mauvais pour la performance. Ce que vous voulez, c'est mettre à jour l'interface une seule fois, à la fin. Les événements de changement sont trop fins.
  • Les changements d'écouteurs se déclenchent immédiatement sur un setter, ce qui est un problème, car l'écouteur change change d'informations, ce qui déclenche plus d'événements de changement. C'est mauvais car sur votre pile vous pouvez avoir plusieurs événements de changement se produisant à la fois. Supposons que vous ayez deux tableaux qui doivent être synchronisés pour une raison quelconque. Vous pouvez seulement ajouter à l'un ou l'autre, mais chaque fois que vous ajoutez vous déclenchez un événement de changement, qui a maintenant une vision incohérente du monde. C'est un problème très similaire au verrouillage des threads, que JavaScript évite puisque chaque callback s'exécute exclusivement et à l'achèvement. Les événements de changement cassent ceci puisque les setters peuvent avoir des conséquences profondes qui ne sont pas voulues et non évidentes, ce qui crée à nouveau le problème du thread. Il s'avère que ce que vous voulez faire est de retarder l'exécution de l'écouteur, et garantir qu'un seul écouteur s'exécute à la fois, donc tout code est libre de changer les données, et il sait qu'aucun autre code ne s'exécute pendant qu'il le fait .

Qu'en est-il des performances?

Donc, il peut sembler que nous sommes lents, puisque la vérification de la saleté est inefficace. C'est là que nous devons regarder des nombres réels plutôt que de simplement avoir des arguments théoriques, mais d'abord nous allons définir quelques contraintes.

Les humains sont:

  • Lent - Tout ce qui dépasse 50 ms est imperceptible aux humains et peut donc être considéré comme "instantané".

  • Limité - Vous ne pouvez pas vraiment montrer plus de 2000 informations à un humain sur une seule page. Quelque chose de plus que cela est une interface utilisateur vraiment mauvaise, et les humains ne peuvent pas traiter cela de toute façon.

Donc la vraie question est la suivante: Combien de comparaisons pouvez-vous faire sur un navigateur en 50 ms? C'est une question difficile à répondre car de nombreux facteurs entrent en jeu, mais voici un cas de test: http://jsperf.com/angularjs-digest/6 ce qui crée 10 000 observateurs. Sur un navigateur moderne, cela prend un peu moins de 6 ms. Sur Internet Explorer 8 cela prend environ 40 ms. Comme vous pouvez le voir, ce n'est pas un problème même sur les navigateurs lents ces jours-ci. Il y a une mise en garde: les comparaisons doivent être simples pour tenir dans la limite de temps ... Malheureusement, il est trop facile d'ajouter une comparaison lente dans AngularJS, il est donc facile de construire des applications lentes lorsque vous ne savez pas ce que vous faites. Mais nous espérons avoir une réponse en fournissant un module d'instrumentation, qui vous montrerait quelles sont les comparaisons lentes.

Il s'avère que les jeux vidéo et les GPUs utilisent l'approche du dirty-checking, notamment parce qu'elle est cohérente. Tant qu'ils dépassent le taux de rafraîchissement du moniteur (généralement 50-60 Hz, ou toutes les 16,6-20 ms), toute performance est un gaspillage, il est donc préférable de tirer plus de choses que d'augmenter le FPS.


2661
2018-03-13 23:47



Misko a déjà donné une excellente description du fonctionnement des liaisons de données, mais je voudrais ajouter mon point de vue sur le problème de performance avec la liaison de données.

Comme l'a déclaré Misko, environ 2000 liaisons sont là où vous commencez à voir des problèmes, mais vous ne devriez pas avoir plus de 2000 informations sur une page de toute façon. Cela peut être vrai, mais toutes les liaisons de données ne sont pas visibles pour l'utilisateur. Une fois que vous commencez à construire n'importe quel type de widget ou de grille de données avec une liaison bidirectionnelle, vous pouvez facilement frappé 2000 liaisons, sans avoir un mauvais ux.

Considérons, par exemple, une liste déroulante où vous pouvez taper du texte pour filtrer les options disponibles. Ce type de contrôle pourrait contenir environ 150 objets et être toujours très utilisable. S'il a une fonctionnalité supplémentaire (par exemple une classe spécifique sur l'option actuellement sélectionnée), vous commencez à obtenir 3-5 liaisons par option. Placez trois de ces widgets sur une page (par exemple un pour sélectionner un pays, l'autre pour sélectionner une ville dans ce pays, et le troisième pour sélectionner un hôtel) et vous êtes déjà entre 1000 et 2000 liaisons.

Ou envisagez une grille de données dans une application Web d'entreprise. 50 lignes par page n'est pas déraisonnable, chacune pouvant avoir 10-20 colonnes. Si vous construisez ceci avec ng-repeats, et / ou avez des informations dans certaines cellules qui utilisent des bindings, vous pourriez approcher les bindings 2000 avec cette seule grille.

Je trouve que c'est un énorme problème avec AngularJS, et la seule solution que j'ai pu trouver jusqu'ici est de construire des widgets sans utiliser de liaison bidirectionnelle, en utilisant ngOnce, en désenregistrant les watchers et autres trucs similaires, ou en construisant des directives qui construisent le DOM avec jQuery et Manipulation DOM. Je pense que cela va à l'encontre de l'objectif d'utiliser Angular en premier lieu.

J'aimerais entendre des suggestions sur d'autres façons de gérer cela, mais alors peut-être que je devrais écrire ma propre question. Je voulais mettre cela dans un commentaire, mais il s'est avéré que c'était trop long pour ça ...

TL; DR 
La liaison de données peut entraîner des problèmes de performances sur des pages complexes.


308
2017-08-22 13:28



En vérifiant le sale $scope objet

Angulaire maintient un simple array des observateurs dans le $scope objets. Si vous inspectez tout $scope vous trouverez qu'il contient un array appelé $$watchers.

Chaque observateur est un object qui contient entre autres choses

  1. Une expression que l'observateur surveille. Cela pourrait juste être un attribute nom, ou quelque chose de plus compliqué.
  2. Une dernière valeur connue de l'expression. Cela peut être vérifié par rapport à la valeur calculée actuelle de l'expression. Si les valeurs diffèrent, l'observateur déclenchera la fonction et marquera le $scope aussi sale.
  3. Une fonction qui sera exécutée si l'observateur est sale.

Comment les observateurs sont définis

Il y a plusieurs façons de définir un observateur dans AngularJS.

  • Vous pouvez explicitement $watch un attribute sur $scope.

    $scope.$watch('person.username', validateUnique);
    
  • Vous pouvez placer un {{}} interpolation dans votre template (un observateur sera créé pour vous sur le courant $scope).

    <p>username: {{person.username}}</p>
    
  • Vous pouvez demander une directive telle que ng-model pour définir l'observateur pour vous.

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

le $digest cycle vérifie tous les observateurs contre leur dernière valeur

Lorsque nous interagissons avec AngularJS via les canaux normaux (modèle ng, ng-repeat, etc.), un cycle de digestion est déclenché par la directive.

Un cycle de digestion est un première traversée en profondeur de $scope et tous ses enfants. Pour chaque $scope  object, nous itérons sur son $$watchers  array et évaluez toutes les expressions. Si la nouvelle valeur d'expression est différente de la dernière valeur connue, la fonction de l'observateur est appelée. Cette fonction peut recompiler une partie du DOM, recalculer une valeur sur $scope, déclencher un AJAX  request, tout ce dont vous avez besoin.

Chaque portée est traversée et chaque expression de surveillance est évaluée et vérifiée par rapport à la dernière valeur.

Si un observateur est déclenché, le $scope est sale

Si un observateur est déclenché, l'application sait que quelque chose a changé, et le $scope est marqué comme sale.

Les fonctions Watcher peuvent changer d'autres attributs $scope ou sur un parent $scope. Si un $watcher la fonction a été déclenchée, nous ne pouvons pas garantir que nos autres $scopes sont toujours propres et nous exécutons à nouveau tout le cycle de digestion.

C'est parce que AngularJS a une liaison bidirectionnelle, donc les données peuvent être retransmises $scopearbre. Nous pouvons changer une valeur sur un plus haut $scope qui a déjà été digéré. Peut-être que nous changeons une valeur sur le $rootScope.

Si la $digest est sale, nous exécutons l'ensemble $digest cycle encore

Nous continuons en boucle à travers le $digest cycle jusqu'à ce que le cycle de digestion soit propre (tous les $watch les expressions ont la même valeur que dans le cycle précédent), ou nous atteignons la limite de digestion. Par défaut, cette limite est fixée à 10.

Si nous atteignons la limite digest AngularJS va déclencher une erreur dans la console:

10 $digest() iterations reached. Aborting!

Le condensé est dur sur la machine mais facile sur le développeur

Comme vous pouvez le voir, chaque fois que quelque chose change dans une application AngularJS, AngularJS vérifie chaque observateur dans le $scope hiérarchie pour voir comment répondre. Pour un développeur, c'est une aubaine pour la productivité, car vous n'avez plus besoin d'écrire de code de câblage, AngularJS remarquera simplement si une valeur a changé et rendra le reste de l'application compatible avec le changement.

Du point de vue de la machine bien que ce soit follement inefficace et ralentira notre application si nous créons trop d'observateurs. Misko a cité un chiffre d'environ 4000 observateurs avant que votre application se sentira lente sur les anciens navigateurs.

Cette limite est facile à atteindre si vous ng-repeat sur une grande JSON  array par exemple. Vous pouvez atténuer cela en utilisant des fonctionnalités telles que la liaison unique pour compiler un modèle sans créer de surveillance.

Comment éviter de créer trop de watchers

Chaque fois que votre utilisateur interagit avec votre application, chaque observateur de votre application sera évalué au moins une fois. Une grande partie de l'optimisation d'une application AngularJS consiste à réduire le nombre d'observateurs dans votre $scope arbre. Un moyen facile de le faire est avec une fois contraignant.

Si vous avez des données qui vont rarement changer, vous pouvez les lier une seule fois en utilisant la syntaxe ::, comme ceci:

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

ou

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

La liaison ne sera déclenchée que lorsque le modèle contenant est rendu et les données chargées dans $scope.

Ceci est particulièrement important lorsque vous avez un ng-repeat avec beaucoup d'articles.

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

142
2018-06-02 12:31



C'est ma compréhension de base. C'est peut-être faux!

  1. Les éléments sont regardés en passant une fonction (renvoyer la chose à être regardé) à la $watch méthode.
  2. Les modifications apportées aux éléments surveillés doivent être effectuées dans un bloc de code enveloppé par le $apply méthode.
  3. À la fin de $apply la $digest méthode est invoquée qui va à travers chacune des montres et vérifie pour voir si elles ont changé depuis la dernière fois $digest couru.
  4. Si des modifications sont trouvées, le résumé est invoqué à nouveau jusqu'à ce que toutes les modifications se stabilisent.

En développement normal, la syntaxe de liaison de données dans le code HTML indique au compilateur AngularJS de créer les montres pour vous et les méthodes du contrôleur sont exécutées à l'intérieur. $apply déjà. Donc, pour le développeur de l'application, tout est transparent.


77
2018-03-13 21:01



Je me suis demandé cela moi-même pendant un moment. Sans setters comment ça marche AngularJS remarquer les changements à la $scope objet? Est-ce qu'ils les sondent?

Qu'est-ce que cela fait est la suivante: Tout lieu "normal" vous modifiez le modèle a déjà été appelé à partir du ventre de AngularJS, donc il appelle automatiquement $apply pour vous après l'exécution de votre code. Supposons que votre contrôleur possède une méthode connectée à ng-click sur un élément. Car AngularJS fils l'appel de cette méthode ensemble pour vous, il a une chance de faire un $apply à l'endroit approprié. De même, pour les expressions qui apparaissent correctement dans les vues, celles-ci sont exécutées par AngularJS donc il fait le $apply.

Lorsque la documentation parle d'avoir à appeler $apply manuellement pour le code en dehors de AngularJS, il s'agit de code qui, lorsqu'il est exécuté, ne provient pas de AngularJS lui-même dans la pile d'appels.


57
2017-09-03 17:45



Expliquer avec des images:

Data-Binding a besoin d'une cartographie

La référence dans la portée n'est pas exactement la référence dans le modèle. Lorsque vous liez deux objets, vous avez besoin d'un troisième qui écoute le premier et modifie l'autre.

enter image description here

Ici, quand vous modifiez le <input>, vous touchez le data-ref3. Et le mécanisme classique de liaison de données va changer data-ref4. Alors comment l'autre {{data}} les expressions vont bouger?

Les événements mènent à $ digest ()

enter image description here

Angulaire maintient un oldValue et newValue de chaque liaison. Et après chaque Evénement angulaire, le célèbre $digest() loop vérifiera le WatchList pour voir si quelque chose a changé. Celles-ci Evénements angulaires sont ng-click, ng-change, $http terminé ... Le $digest() bouclera aussi longtemps que tout oldValue diffère de la newValue.

Dans l'image précédente, il remarquera que data-ref1 et data-ref2 ont changé.

Conclusions

C'est un peu comme l'oeuf et le poulet. Vous ne savez jamais qui commence, mais j'espère que cela fonctionne la plupart du temps comme prévu.

L'autre point est que vous pouvez comprendre facilement l'impact profond d'une simple liaison sur la mémoire et le CPU. Espérons que les ordinateurs de bureau sont assez gros pour gérer cela. Les téléphones mobiles ne sont pas si forts.


29
2018-05-20 13:33



De toute évidence, il n'y a pas de vérification périodique Scope s'il y a un changement dans les objets attachés à celui-ci. Tous les objets attachés à la portée ne sont pas surveillés. Scope prototypiquement maintient un $$ observateurs . Scope seulement itère à travers cette $$watchers quand $digest est appelé .

Angular ajoute un observateur aux observateurs de $$ pour chacun d'eux

  1. {{expression}} - Dans vos modèles (et partout ailleurs où il y a une expression) ou quand nous définissons ng-model.
  2. $ scope. $ watch ('expression / function') - Dans votre JavaScript, nous pouvons simplement attacher un objet scope pour angulaire à regarder.

$ watch La fonction prend trois paramètres:

  1. Le premier est une fonction observateur qui retourne simplement l'objet ou nous pouvons simplement ajouter une expression.

  2. La seconde est une fonction d'écoute qui sera appelée quand il y a un changement dans l'objet. Toutes les choses comme les changements DOM seront implémentées dans cette fonction.

  3. Le troisième étant un paramètre optionnel qui prend un booléen. Si son vrai, profond angulaire observe l'objet & si son faux Angulaire fait juste une référence en regardant sur l'objet.     La mise en œuvre approximative de $ watch ressemble à ceci

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

Il y a une chose intéressante dans Angular appelée Digest Cycle. Le cycle $ digest commence à la suite d'un appel à $ scope. $ Digest (). Supposons que vous modifiez un modèle $ scope dans une fonction de gestionnaire via la directive ng-click. Dans ce cas, AngularJS déclenche automatiquement un cycle $ digest en appelant $ digest (). En plus de ng-click, il existe plusieurs autres directives / services intégrés qui vous permettent de changer de modèle (par exemple ng-model, $ timeout, etc.) et déclenche automatiquement un cycle $ digest. L'implémentation approximative de $ digest ressemble à ceci.

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

Si nous utilisons JavaScript setTimeout () fonction pour mettre à jour un modèle de portée, Angular n'a aucun moyen de savoir ce que vous pourriez changer. Dans ce cas, il est de notre responsabilité d'appeler $ apply () manuellement, ce qui déclenche un cycle $ digest. De même, si vous avez une directive qui configure un écouteur d'événement DOM et modifie certains modèles dans la fonction de gestionnaire, vous devez appeler $ apply () pour vous assurer que les modifications prennent effet. La grande idée de $ apply est que nous pouvons exécuter du code qui ne connaît pas Angular, ce code peut encore changer les choses sur la portée. Si nous appliquons ce code dans $ apply, il se chargera d'appeler $ digest (). Implémentation approximative de $ 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 gère le mécanisme de liaison de données à l'aide de trois fonctions puissantes: $ watch (),$ digest ()et $ apply (). La plupart du temps AngularJS appellera $ scope. $ Watch () et $ scope. $ Digest (), mais Dans certains cas, vous devrez peut-être appeler ces fonctions manuellement pour les mettre à jour avec de nouvelles valeurs.

$ watch () : -

Cette fonction est utilisée pour observer les changements dans une variable sur $ scope.   Il accepte trois paramètres: expression, écouteur et objet égalité,   où l'écouteur et l'objet d'égalité sont des paramètres facultatifs.

$ digest () -

Cette fonction parcourt toutes les montres de l'objet $ scope   et ses objets enfant $ scope
     (s'il en a). Quand $ digest () itère   sur les montres, il vérifie si la valeur de l'expression a   modifié. Si la valeur a changé, AngularJS appelle l'auditeur avec   nouvelle valeur et ancienne valeur. La fonction $ digest () est appelée   chaque fois qu'AngularJS pense que c'est nécessaire. Par exemple, après un bouton   cliquez sur, ou après un appel AJAX. Vous pouvez avoir des cas où AngularJS   n'appelle pas la fonction $ digest () pour vous. Dans ce cas, vous devez   appelez-le vous-même.

$ apply () -

Angulaire ne met à jour automatiquement que les changements de modèle qui sont   dans le contexte AngularJS. Lorsque vous changez de modèle en dehors de   le contexte angulaire (comme les événements DOM du navigateur, setTimeout, XHR ou   bibliothèques de parti), alors vous devez informer Angular des changements par   appeler $ apply () manuellement. Lorsque l'appel de la fonction $ apply () se termine   AngularJS appelle $ digest () en interne, donc toutes les liaisons de données sont   actualisé.


12
2018-05-16 15:05