Maison > interface Web > js tutoriel > Explication détaillée des principes de liaison de données dans le didacticiel d'introduction d'AngularJS

Explication détaillée des principes de liaison de données dans le didacticiel d'introduction d'AngularJS

高洛峰
Libérer: 2016-12-24 10:03:31
original
892 Les gens l'ont consulté

Les exemples de cet article décrivent les principes de la liaison de données AngularJS. Partagez-le avec tout le monde pour votre référence, les détails sont les suivants :

Remarque

Cet article est principalement écrit pour les novices, pour ceux qui viennent de commencer à contacter Angular et souhaitent comprendre comment les données l'assistant travaille. Si vous en savez déjà beaucoup sur Angular, il est fortement recommandé d’aller directement lire le code source.

Les utilisateurs d'Angular veulent tous savoir comment la liaison de données est implémentée. Vous pouvez voir différents mots : $watch, $apply, $digest, dirty-checking... Quels sont-ils ? Comment fonctionnent-ils ? Ici, je veux répondre à ces questions. En fait, elles ont trouvé des réponses dans les documents officiels, mais je veux toujours les combiner, mais j'utilise simplement une méthode simple pour expliquer. Si vous voulez comprendre les détails techniques, voir la source. code.

Commençons par le début.

Boucle d'événements du navigateur et extensions Angular.js

Notre navigateur attend toujours des événements, tels que les interactions des utilisateurs. Si vous cliquez sur un bouton ou entrez quelque chose dans la zone de saisie, la fonction de rappel de l'événement sera exécutée dans l'interpréteur JavaScript, et vous pourrez alors effectuer n'importe quelle opération DOM. Lorsque la fonction de rappel est terminée, le navigateur fonctionnera sur le DOM. en conséquence. Apportez des modifications. Angular étend cette boucle d'événements pour générer un environnement d'exécution qui est parfois appelé contexte angulaire (rappelez-vous qu'il s'agit d'un concept important). Afin d'expliquer ce qu'est le contexte et comment il fonctionne, nous devons expliquer quelques concepts supplémentaires.

$watch queue ($watch list)

Chaque fois que vous liez quelque chose à votre interface utilisateur, vous insérez un $watch dans la file d'attente $watch. Imaginez que $watch soit quelque chose qui puisse détecter les changements dans le modèle qu'il surveille. Par exemple, vous avez le code suivant

index.html

User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />
Copier après la connexion

Ici nous avons $scope.user, qui est lié à la première zone de saisie, il y a aussi $scope.pass, qui est lié à la deuxième zone de saisie. Ensuite, nous ajoutons deux $watch

à la liste $watch et continuons à regarder les exemples ci-dessous :

<. 🎜>controllers.js

app.controller(&#39;MainCtrl&#39;, function($scope) {
 $scope.foo = "Foo";
 $scope.world = "World";
});
Copier après la connexion

index.html

Hello, {{ World }}
Copier après la connexion

Ici, même si nous avons ajouté deux éléments à $scope, un seul est lié à l'interface utilisateur, donc un seul $watch est généré ici.

Regardez à nouveau Exemples ci-dessous :

controllers.js

app.controller(&#39;MainCtrl&#39;, function($scope) {
 $scope.people = [...];
});
Copier après la connexion

index.html

<ul>
 <li ng-repeat="person in people">
   {{person.name}} - {{person.age}}
 </li>
</ul>
Copier après la connexion

Combien de $watches sont générés ici? Chaque personne en a deux (un nom, un âge), puis une répétition, donc le total de 10 personnes est (2 * 10) 1, ce qui signifie qu'il y a 21 $watches. Par conséquent, chaque donnée liée à l’interface utilisateur générera un $watch. Oui, quand $watch a-t-il été généré ? Lorsque notre modèle est chargé, c'est-à-dire dans la phase de liaison (Angular est divisé en phase de compilation et phase de liaison --- Note du traducteur), l'interpréteur Angular recherchera chaque directive et générera chaque $watch requis. Cela semble bien, mais et ensuite ?

$digest loop

Vous vous souvenez de la boucle d'événements étendue que j'ai mentionnée plus tôt ? La boucle $digest est déclenchée lorsque le navigateur reçoit un événement pouvant être géré par le contexte angulaire. Cette boucle est composée de deux boucles plus petites. L’une gère la file d’attente evalAsync et l’autre la file d’attente $watch, qui fait également l’objet de cet article de blog. De quoi s’agit-il ? $digest parcourra notre $watch et demandera :

Hé $watch, quelle est votre valeur ?

Il est 9 heures.
D'accord, est-ce que ça a changé ?
Non, monsieur.
(Cette variable n'a pas changé, puis la suivante)
Et vous, quelle est votre valeur ?
Rapport, c'est Foo.
Avez-vous changé quelque chose à l'instant ?
Changé, c'était Bar tout à l'heure.
(Bien, nous avons un DOM qui doit être mis à jour)
Continuez à demander jusqu'à ce que la file d'attente $watch ait été vérifiée.

C'est ce qu'on appelle une vérification sale. Maintenant que tous les $watchs ont été vérifiés, nous devons nous demander : $watch a-t-il été mis à jour ? Si au moins un a été mis à jour, la boucle sera à nouveau déclenchée jusqu'à ce que tous les $watches soient inchangés. Cela garantit que chaque modèle ne changera plus. N'oubliez pas que si la boucle dépasse 10 fois, une exception sera levée pour empêcher une boucle infinie. Lorsque la boucle $digest se termine, le DOM change en conséquence.

Par exemple : contrôleurs.js

app.controller(&#39;MainCtrl&#39;, function() {
 $scope.name = "Foo";
 $scope.changeFoo = function() {
   $scope.name = "Bar";
 }
});
Copier après la connexion

index.html

{{ name }}
<button ng-click="changeFoo()">Change the name</button>
Copier après la connexion

Ici nous avons un $watch car ng-click ne génère pas $watch (la fonction ne changera pas).

On appuie sur le bouton

Le navigateur reçoit un événement et entre dans le contexte angulaire (pourquoi sera expliqué plus tard).

La boucle $digest commence à s'exécuter, demandant si chaque $watch change.

Étant donné que $watch surveillant $scope.name signale un changement, il forcera un autre cycle $digest.

Nouvelle boucle $digest sans aucun changement détecté.

Le navigateur reprend le contrôle et met à jour la partie du DOM correspondant à la nouvelle valeur de $scope.name.

Ce qui est très important ici (et constitue un problème pour beaucoup de gens), c'est que chaque événement qui entre dans le contexte angulaire exécutera une boucle $digest, ce qui signifie que chaque fois que nous entrons une lettre, la boucle consultez la page entière de tous les $watch.

Entrez le contexte angulaire via $apply

谁决定什么事件进入angular context,而哪些又不进入呢?$apply!

如果当事件触发时,你调用$apply,它会进入angular context,如果没有调用就不会进入。现在你可能会问:刚才的例子里我也没有调用$apply啊,为什么?Angular为了做了!因此你点击带有ng-click的元素时,时间就会被封装到一个$apply调用。如果你有一个ng-model="foo"的输入框,然后你敲一个f,事件就会这样调用$apply("foo = 'f';")。

Angular什么时候不会自动为我们$apply呢?

这是Angular新手共同的痛处。为什么我的jQuery不会更新我绑定的东西呢?因为jQuery没有调用$apply,事件没有进入angular context,$digest循环永远没有执行。

我们来看一个有趣的例子:

假设我们有下面这个directive和controller

app.js

app.directive(&#39;clickable&#39;, function() {
return {
 restrict: "E",
 scope: {
  foo: &#39;=&#39;,
  bar: &#39;=&#39;
 },
 template: &#39;<ul style="background-color: lightblue"><li>{{foo}}</li><li>{{bar}}</li></ul>&#39;,
 link: function(scope, element, attrs) {
  element.bind(&#39;click&#39;, function() {
   scope.foo++;
   scope.bar++;
  });
 }
}
});
app.controller(&#39;MainCtrl&#39;, function($scope) {
 $scope.foo = 0;
 $scope.bar = 0;
});
Copier après la connexion

它将foo和bar从controller里绑定到一个list里面,每次点击这个元素的时候,foo和bar都会自增1。

那我们点击元素的时候会发生什么呢?我们能看到更新吗?答案是否定的。因为点击事件是一个没有封装到$apply里面的常见的事件,这意味着我们会失去我们的计数吗?不会

真正的结果是:$scope确实改变了,但是没有强制$digest循环,监视foo 和bar的$watch没有执行。也就是说如果我们自己执行一次$apply那么这些$watch就会看见这些变化,然后根据需要更新DOM。

试试看吧:http://jsbin.com/opimat/2/

如果我们点击这个directive(蓝色区域),我们看不到任何变化,但是我们点击按钮时,点击数就更新了。如刚才说的,在这个directive上点击时我们不会触发$digest循环,但是当按钮被点击时,ng-click会调用$apply,然后就会执行$digest循环,于是所有的$watch都会被检查,当然就包括我们的foo和bar的$watch了。

现在你在想那并不是你想要的,你想要的是点击蓝色区域的时候就更新点击数。很简单,执行一下$apply就可以了:

element.bind(&#39;click&#39;, function() {
 scope.foo++;
 scope.bar++;
 scope.$apply();
});
Copier après la connexion

$apply是我们的$scope(或者是direcvie里的link函数中的scope)的一个函数,调用它会强制一次$digest循环(除非当前正在执行循环,这种情况下会抛出一个异常,这是我们不需要在那里执行$apply的标志)。

试试看:http://jsbin.com/opimat/3/edit

有用啦!但是有一种更好的使用$apply的方法:

element.bind(&#39;click&#39;, function() {
 scope.$apply(function() {
   scope.foo++;
   scope.bar++;
 });
})
Copier après la connexion

有什么不一样的?差别就是在第一个版本中,我们是在angular context的外面更新的数据,如果有发生错误,Angular永远不知道。很明显在这个像个小玩具的例子里面不会出什么大错,但是想象一下我们如果有个alert框显示错误给用户,然后我们有个第三方的库进行一个网络调用然后失败了,如果我们不把它封装进$apply里面,Angular永远不会知道失败了,alert框就永远不会弹出来了。

因此,如果你想使用一个jQuery插件,并且要执行$digest循环来更新你的DOM的话,要确保你调用了$apply。

有时候我想多说一句的是有些人在不得不调用$apply时会“感觉不妙”,因为他们会觉得他们做错了什么。其实不是这样的,Angular不是什么魔术师,他也不知道第三方库想要更新绑定的数据。

使用$watch来监视你自己的东西

你已经知道了我们设置的任何绑定都有一个它自己的$watch,当需要时更新DOM,但是我们如果要自定义自己的watches呢?简单

来看个例子:

app.js

app.controller(&#39;MainCtrl&#39;, function($scope) {
 $scope.name = "Angular";
 $scope.updated = -1;
 $scope.$watch(&#39;name&#39;, function() {
  $scope.updated++;
 });
});
Copier après la connexion

index.html

<body ng-controller="MainCtrl">
 <input ng-model="name" />
 Name updated: {{updated}} times.
</body>
Copier après la connexion
Copier après la connexion

这就是我们创造一个新的$watch的方法。第一个参数是一个字符串或者函数,在这里是只是一个字符串,就是我们要监视的变量的名字,在这里,$scope.name(注意我们只需要用name)。第二个参数是当$watch说我监视的表达式发生变化后要执行的。我们要知道的第一件事就是当controller执行到这个$watch时,它会立即执行一次,因此我们设置updated为-1。

试试看:http://jsbin.com/ucaxan/1/edit

例子2:

app.js

app.controller(&#39;MainCtrl&#39;, function($scope) {
 $scope.name = "Angular";
 $scope.updated = 0;
 $scope.$watch(&#39;name&#39;, function(newValue, oldValue) {
  if (newValue === oldValue) { return; } // AKA first run
  $scope.updated++;
 });
});
Copier après la connexion

index.html

<body ng-controller="MainCtrl">
 <input ng-model="name" />
 Name updated: {{updated}} times.
</body>
Copier après la connexion
Copier après la connexion

watch的第二个参数接受两个参数,新值和旧值。我们可以用他们来略过第一次的执行。通常你不需要略过第一次执行,但在这个例子里面你是需要的。灵活点嘛少年。

例子3:

app.js

app.controller(&#39;MainCtrl&#39;, function($scope) {
 $scope.user = { name: "Fox" };
 $scope.updated = 0;
 $scope.$watch(&#39;user&#39;, function(newValue, oldValue) {
  if (newValue === oldValue) { return; }
  $scope.updated++;
 });
});
Copier après la connexion

index.html

<body ng-controller="MainCtrl">
 <input ng-model="user.name" />
 Name updated: {{updated}} times.
</body>
Copier après la connexion

我们想要监视$scope.user对象里的任何变化,和以前一样这里只是用一个对象来代替前面的字符串。

试试看:http://jsbin.com/ucaxan/3/edit

呃?没用,为啥?因为$watch默认是比较两个对象所引用的是否相同,在例子1和2里面,每次更改$scope.name都会创建一个新的基本变量,因此$watch会执行,因为对这个变量的引用已经改变了。在上面的例子里,我们在监视$scope.user,当我们改变$scope.user.name时,对$scope.user的引用是不会改变的,我们只是每次创建了一个新的$scope.user.name,但是$scope.user永远是一样的。

例子4:

app.js

app.controller(&#39;MainCtrl&#39;, function($scope) {
 $scope.user = { name: "Fox" };
 $scope.updated = 0;
 $scope.$watch(&#39;user&#39;, function(newValue, oldValue) {
  if (newValue === oldValue) { return; }
  $scope.updated++;
 }, true);
});
Copier après la connexion

index.html

<body ng-controller="MainCtrl">
 <input ng-model="user.name" />
 Name updated: {{updated}} times.
</body>
Copier après la connexion

   

试试看:http://jsbin.com/ucaxan/4/edit

现在有用了吧!因为我们对$watch加入了第三个参数,它是一个bool类型的参数,表示的是我们比较的是对象的值而不是引用。由于当我们更新$scope.user.name时$scope.user也会改变,所以能够正确触发。

关于$watch还有很多tips&tricks,但是这些都是基础。

总结

好吧,我希望你们已经学会了在Angular中数据绑定是如何工作的。我猜想你的第一印象是dirty-checking很慢,好吧,其实是不对的。它像闪电般快。但是,是的,如果你在一个模版里有2000-3000个watch,它会开始变慢。但是我觉得如果你达到这个数量级,就可以找个用户体验专家咨询一下了

无论如何,随着ECMAScript6的到来,在Angular未来的版本里我们将会有Object.observe那样会极大改善$digest循环的速度。同时未来的文章也会涉及一些tips&tricks。

另一方面,这个主题并不容易,如果你发现我落下了什么重要的东西或者有什么东西完全错了,请指正(原文是在GITHUB上PR 或报告issue)

希望本文所述对大家AngularJS程序设计有所帮助。

更多AngularJS入门教程之数据绑定原理详解相关文章请关注PHP中文网!


Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal