Comment fonctionne la liaison de données dans AngularJS ?

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

Je n'ai pas trouvé de détails techniques sur [leur site][1]. Le fonctionnement est plus ou moins clair lorsque les données sont propagées de la vue au modèle. Mais comment AngularJS suit-il les modifications des propriétés du modèle sans setters ni getters ?

J'ai découvert qu'il existe des [JavaScript watchers][2] qui peuvent faire ce travail. Mais ils ne sont pas supportés par [Internet Explorer 6][3] et [Internet Explorer 7][4]. Alors comment AngularJS sait-il que j'ai modifié par exemple ce qui suit et que j'ai répercuté ce changement sur une vue ?

myobject.myproperty="new value";

[1] : http://angularjs.org [2] : https://stackoverflow.com/questions/1029241/javascript-object-watch-for-all-browsers [3] : http://en.wikipedia.org/wiki/Internet_Explorer_6 [4] : http://en.wikipedia.org/wiki/Internet_Explorer_7

Solution

AngularJS se souvient de la valeur et la compare à une valeur précédente. Il s'agit d'une vérification de base. S'il y a un changement de valeur, il déclenche l'événement de changement.

La méthode $apply(), qui est ce que vous appelez lorsque vous passez d'un monde non AngularJS à un monde AngularJS, appelle $digest(). Un digest n'est qu'une simple vérification de routine. Il fonctionne sur tous les navigateurs et est totalement prévisible.

Comparons le dirty-checking (AngularJS) aux change listeners ([KnockoutJS][1] et [Backbone.js][2]) : Bien que le dirty-checking puisse sembler simple, et même inefficace (j'y reviendrai plus tard), il s'avère qu'il est sémantiquement correct tout le temps, tandis que les change listeners ont beaucoup de cas de coin bizarres et ont besoin de choses comme le suivi des dépendances pour le rendre plus sémantiquement correct. Le suivi des dépendances de KnockoutJS est une fonctionnalité intelligente pour un problème qu'AngularJS n'a pas.

Problèmes avec les change listeners :

  • La syntaxe est atroce, puisque 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 proxies sur les anciens navigateurs. En résumé, le dirty-checking vous permet de faire du [POJO][3], alors que KnockoutJS et Backbone.js vous obligent à hériter de leurs classes et à accéder à vos données par le biais d'accesseurs.
  • Coalescence des changements. Supposons que vous ayez un tableau d'éléments. Disons que vous voulez ajouter des éléments dans un tableau, comme vous bouclez pour ajouter, à chaque fois que vous ajoutez, vous déclenchez des événements sur le changement, ce qui rend l'interface utilisateur. C'est très mauvais pour les performances. Ce que vous voulez, c'est mettre à jour l'interface utilisateur une seule fois, à la fin. Les événements de changement sont trop fins.
  • Les auditeurs de changement déclenchent immédiatement un setter, ce qui est un problème, car l'auditeur de changement peut modifier les données, ce qui déclenche d'autres événements de changement. C'est une mauvaise chose, car dans votre pile, plusieurs événements de changement peuvent se produire en même temps. 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 vue incohérente du monde. Il s'agit d'un problème très similaire au verrouillage des threads, que JavaScript évite puisque chaque callback s'exécute exclusivement et jusqu'à la fin. Les événements de modification rompent avec ce principe, car les paramètres peuvent avoir des conséquences importantes qui ne sont ni prévues ni évidentes, ce qui crée à nouveau le problème des fils. Il s'avère que ce que vous voulez faire, c'est retarder l'exécution du listener et garantir qu'un seul listener s'exécute à la fois, ce qui permet à n'importe quel code d'être libre de modifier les données, tout en sachant qu'aucun autre code ne s'exécute pendant ce temps.

Qu'en est-il des performances ?

Il peut sembler que nous soyons lents, puisque le dirty-checking est inefficace. C'est là que nous devons examiner des chiffres réels plutôt que de nous contenter d'arguments théoriques, mais définissons d'abord quelques contraintes.

Les humains sont :

Lent - Tout ce qui est plus rapide que 50 ms est imperceptible pour les humains et peut donc être considéré comme "instant&quot ;.

Limité - On ne peut pas vraiment montrer plus de 2000 éléments d'information à un humain sur une seule page. Tout ce qui dépasse ce chiffre est une très mauvaise interface utilisateur, et les humains ne peuvent pas traiter ces informations de toute façon.

La vraie question est donc la suivante : Combien de comparaisons pouvez-vous faire sur un navigateur en 50 ms ? Il est difficile de répondre à cette question car de nombreux facteurs entrent en jeu, mais voici un cas test : http://jsperf.com/angularjs-digest/6 qui crée 10 000 surveillants. Sur un navigateur moderne, cela prend un peu moins de 6 ms. Sur [Internet Explorer 8][4], cela prend environ 40 ms. Comme vous pouvez le constater, ce n'est pas un problème, même sur les navigateurs lents de nos jours. Il y a un bémol : les comparaisons doivent être simples pour tenir dans le temps imparti... Malheureusement, il est beaucoup 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 GPU utilisent l'approche dirty-checking, notamment parce qu'elle est cohérente. Tant qu'ils dépassent le taux de rafraîchissement de l'écran (typiquement 50-60 Hz, ou toutes les 16,6-20 ms), toute performance supérieure à ce taux est un gaspillage, donc il vaut mieux dessiner plus de choses que d'augmenter le nombre de FPS.

[1] : http://en.wikipedia.org/wiki/KnockoutJS [2] : https://en.wikipedia.org/wiki/Backbone.js [3] : http://en.wikipedia.org/wiki/Plain_Old_Java_Object [4] : http://en.wikipedia.org/wiki/Internet_Explorer_8

Commentaires (38)

C'est ma compréhension de base. Elle peut très bien être fausse !

  1. Les éléments sont surveillés en passant une fonction (retournant la chose à être à surveiller) à la méthode $watch.
  2. Les modifications des éléments surveillés doivent être faites dans un bloc de code enveloppé par la méthode $apply.
  3. A la fin de la méthode $apply, la méthode $digest est invoquée. chacune des surveillances et vérifie si elles ont changé depuis la dernière la dernière fois que la méthode $digest a été exécutée.
  4. Si des changements sont trouvés, alors le digest est invoqué à nouveau jusqu'à ce que tous les changements se stabilisent.

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

Commentaires (4)

Je me suis moi-même posé cette question pendant un certain temps. Sans setters, comment AngularJS remarque-t-il les changements de l'objet $scope ? Est-ce qu'il les interroge ?

Ce qu'il fait en fait est ceci : Tout endroit "normal" où vous modifiez le modèle a déjà été appelé depuis les entrailles de AngularJS, donc il appelle automatiquement $apply pour vous après l'exécution de votre code. Imaginons que votre contrôleur dispose d'une méthode liée à un " clic " sur un élément. Comme AngularJS câble l'appel de cette méthode pour vous, il a la possibilité de faire un $apply à l'endroit approprié. De même, pour les expressions qui apparaissent directement dans les vues, elles sont exécutées par AngularJS pour qu'il fasse le $apply.

Lorsque la documentation indique qu'il faut appeler manuellement $apply pour le code extérieur à AngularJS, elle parle du code qui, lorsqu'il est exécuté, ne provient pas de AngularJS lui-même dans la pile d'appels.

Commentaires (0)