Maison > interface Web > js tutoriel > En savoir plus sur le mécanisme de liaison bidirectionnelle des données dans AngularJS

En savoir plus sur le mécanisme de liaison bidirectionnelle des données dans AngularJS

高洛峰
Libérer: 2016-12-24 10:18:39
original
957 Les gens l'ont consulté

Angular JS (Angular.JS) est un ensemble de frameworks, de modèles, de liaisons de données et de composants d'interface utilisateur riches utilisés pour développer des pages Web. Il prend en charge l'ensemble du processus de développement et fournit l'architecture des applications Web sans manipulation manuelle du DOM. AngularJS est petit, seulement 60 Ko, compatible avec les navigateurs grand public et fonctionne bien avec jQuery. La liaison de données bidirectionnelle est peut-être la fonctionnalité la plus intéressante et la plus pratique d'AngularJS, qui démontre pleinement le principe de MVC.

Le principe de fonctionnement d'AngularJS est le suivant : le modèle HTML sera analysé dans le DOM par le navigateur, et la structure DOM devient l'entrée du compilateur AngularJS. AngularJS parcourra le modèle DOM pour générer les instructions NG correspondantes. Toutes les instructions sont responsables de la définition de la liaison de données pour la vue (c'est-à-dire ng-model en HTML). Par conséquent, le framework NG ne commence à fonctionner qu'après le chargement du DOM

en html :

<body ng-app="ngApp">
 <div ng-controller="ngCtl">
  <label ng-model="myLabel"></label>
  <input type="text" ng-model="myInput" />
  <button ng-model="myButton" ng-click="btnClicked"></button>
 </div>
</body>
Copier après la connexion


En js. :

// angular app
var app = angular.module("ngApp", [], function(){
 console.log("ng-app : ngApp");
});
// angular controller
app.controller("ngCtl", [ &#39;$scope&#39;, function($scope){
 console.log("ng-controller : ngCtl");
 $scope.myLabel = "text for label";
 $scope.myInput = "text for input";
 $scope.btnClicked = function() {
  console.log("Label is " + $scope.myLabel);
 }
}]);
Copier après la connexion

Comme ci-dessus, nous définissons d'abord une application angulaire en html et spécifions un contrôleur angulaire, puis le contrôleur correspondra à une portée (peut utiliser le préfixe $scope pour spécifier les propriétés et méthodes dans la portée, etc.). La valeur ou le fonctionnement de la balise HTML dans la portée de ngCtl peut être liée aux propriétés et méthodes dans js via $scope.

De cette façon, la liaison de données bidirectionnelle de NG est réalisée : c'est-à-dire que la vue présentée en HTML est cohérente avec les données dans AngularJS. Si l'une est modifiée, l'autre extrémité correspondante changera également en conséquence.

Cette méthode est. vraiment très pratique à utiliser. Nous ne nous soucions que du style des balises HTML et des propriétés et méthodes correspondantes liées dans le cadre du contrôleur angulaire en js. C'est tout, nous combinerons de nombreux DOM complexes. Toutes les opérations sont omises. 🎜> Cette idée est en fait complètement différente de la requête et du fonctionnement du DOM de jQuery. Par conséquent, de nombreuses personnes suggèrent de ne pas mélanger jQuery lors de l'utilisation d'AngularJS. Bien sûr, les deux ont leurs propres différences, dont celui à utiliser dépend. votre choix.

L'application dans NG est équivalente à un module. Plusieurs contrôleurs peuvent être définis dans chaque application, et chaque contrôleur aura son propre espace et n'interférera pas les uns avec les autres. 🎜>Comment les données de liaison prennent-elles effet ?

Les débutants dans AngularJS peuvent entrer dans un tel gouffre Supposons qu'il y ait une instruction :


var app = angular.module("test", []);
 
app.directive("myclick", function() {
  return function (scope, element, attr) {
    element.on("click", function() {
      scope.counter++;
    });
  };
});
 
app.controller("CounterCtrl", function($scope) {
  $scope.counter = 0;
});
<body ng-app="test">
  <div ng-controller="CounterCtrl">
    <button myclick>increase</button>
    <span ng-bind="counter"></span>
  </div>
</body>
Copier après la connexion

<. 🎜>À ce moment-là, lorsque vous cliquez sur le bouton, le numéro sur l'interface n'augmentera pas. Beaucoup de gens seront confus car ils vérifient le débogueur et constatent que les données ont effectivement augmenté. Pourquoi l'interface n'est-elle pas actualisée lorsque les données changent ?

Essayez d'ajouter scope.digest(); après scope.counter ; et voyez si cela fonctionne ?

Pourquoi devriez-vous faire cela ? Dans quelles circonstances devriez-vous faire cela ? Nous avons constaté qu'il n'y avait pas de résumé dans le premier exemple, et si vous écrivez un résumé, une exception sera également levée indiquant que d'autres résumés sont en cours. Que se passe-t-il ?

Réfléchissons-y d'abord, et si nous voulions implémenter nous-mêmes une telle fonction sans AngularJS ?

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>two-way binding</title>
  </head>
  <body onload="init()">
    <button ng-click="inc">
      increase 1
    </button>
    <button ng-click="inc2">
      increase 2
    </button>
    <span style="color:red" ng-bind="counter"></span>
    <span style="color:blue" ng-bind="counter"></span>
    <span style="color:green" ng-bind="counter"></span>
 
    <script type="text/javascript">
      /* 数据模型区开始 */
      var counter = 0;
 
      function inc() {
        counter++;
      }
 
      function inc2() {
        counter+=2;
      }
      /* 数据模型区结束 */
 
      /* 绑定关系区开始 */
      function init() {
        bind();
      }
 
      function bind() {
        var list = document.querySelectorAll("[ng-click]");
        for (var i=0; i<list.length; i++) {
          list[i].onclick = (function(index) {
            return function() {
              window[list[index].getAttribute("ng-click")]();
              apply();
            };
          })(i);
        }
      }
 
      function apply() {
        var list = document.querySelectorAll("[ng-bind=&#39;counter&#39;]");
        for (var i=0; i<list.length; i++) {
          list[i].innerHTML = counter;
        }
      }
      /* 绑定关系区结束 */
    </script>
  </body>
</html>
Copier après la connexion

Comme vous pouvez le voir, dans un exemple aussi simple, nous avons réalisé une liaison bidirectionnelle. Du clic de deux boutons au changement de données, c'est facile à comprendre, mais nous n'avons pas utilisé directement la méthode onclick du DOM. Au lieu de cela, nous avons créé un ng-click, puis retiré la fonction correspondant à ce ng. -click in bind. Se lie à la fonction de gestionnaire d'événements onclick. Pourquoi est-ce ainsi ? Car bien que les données aient changé, elles n'ont pas été renseignées dans l'interface, nous devons donc effectuer quelques opérations supplémentaires ici.

Sous un autre aspect, lorsque les données changent, ce changement doit être appliqué à l'interface, c'est-à-dire aux trois travées. Mais comme Angular utilise la détection sale, cela signifie qu'après avoir modifié les données, vous devez faire quelque chose vous-même pour déclencher la détection sale, puis l'appliquer à l'élément DOM correspondant aux données. La question est, comment déclencher une détection sale ? Quand se déclenche-t-il ?

Nous savons que certains frameworks basés sur des setters peuvent réaffecter des variables liées sur les éléments DOM lors de la définition des valeurs des données. Le mécanisme de détection sale n'a pas cette étape. Il n'a aucun moyen d'être averti immédiatement après les modifications de données, vous pouvez donc uniquement appeler manuellement apply() dans chaque entrée d'événement pour appliquer les modifications de données à l'interface. Dans l'implémentation réelle d'Angular, une détection sale est d'abord effectuée pour déterminer si les données ont changé, puis la valeur de l'interface est définie.

Donc, nous encapsulons le vrai clic dans ng-click. La fonction la plus importante est d'ajouter un apply() plus tard pour appliquer les modifications de données à l'interface.

Alors, pourquoi une erreur apparaît-elle lors de l'appel de $digest dans ng-click ? En raison de la conception d'Angular, un seul $digest est autorisé à s'exécuter en même temps, et l'instruction intégrée ng-click a déjà déclenché $digest, et l'instruction actuelle n'est pas encore terminée, donc une erreur s'est produite.

$digest et $apply

Dans Angular, il y a deux fonctions, $apply et $digest Nous venons d'utiliser $digest pour appliquer ces données à l'interface. Mais à l’heure actuelle, vous pouvez également utiliser $apply au lieu de $digest. L’effet est le même. Alors, quelle est la différence entre eux ?

最直接的差异是,$apply可以带参数,它可以接受一个函数,然后在应用数据之后,调用这个函数。所以,一般在集成非Angular框架的代码时,可以把代码写在这个里面调用。

var app = angular.module("test", []);
 
app.directive("myclick", function() {
  return function (scope, element, attr) {
    element.on("click", function() {
      scope.counter++;
      scope.$apply(function() {
        scope.counter++;
      });
    });
  };
});
 
app.controller("CounterCtrl", function($scope) {
  $scope.counter = 0;
});
Copier après la connexion


除此之外,还有别的区别吗?

在简单的数据模型中,这两者没有本质差别,但是当有层次结构的时候,就不一样了。考虑到有两层作用域,我们可以在父作用域上调用这两个函数,也可以在子作用域上调用,这个时候就能看到差别了。

对于$digest来说,在父作用域和子作用域上调用是有差别的,但是,对于$apply来说,这两者一样。我们来构造一个特殊的示例:

var app = angular.module("test", []);
 
app.directive("increasea", function() {
  return function (scope, element, attr) {
    element.on("click", function() {
      scope.a++;
      scope.$digest();
    });
  };
});
 
app.directive("increaseb", function() {
  return function (scope, element, attr) {
    element.on("click", function() {
      scope.b++;
      scope.$digest();  //这个换成$apply即可
    });
  };
});
 
app.controller("OuterCtrl", ["$scope", function($scope) {
  $scope.a = 1;
 
  $scope.$watch("a", function(newVal) {
    console.log("a:" + newVal);
  });
 
  $scope.$on("test", function(evt) {
    $scope.a++;
  });
}]);
 
app.controller("InnerCtrl", ["$scope", function($scope) {
  $scope.b = 2;
 
  $scope.$watch("b", function(newVal) {
    console.log("b:" + newVal);
    $scope.$emit("test", newVal);
  });
}]);
<div ng-app="test">
  <div ng-controller="OuterCtrl">
    <div ng-controller="InnerCtrl">
      <button increaseb>increase b</button>
      <span ng-bind="b"></span>
    </div>
    <button increasea>increase a</button>
    <span ng-bind="a"></span>
  </div>
</div>
Copier après la connexion


这时候,我们就能看出差别了,在increase b按钮上点击,这时候,a跟b的值其实都已经变化了,但是界面上的a没有更新,直到点击一次increase a,这时候刚才对a的累加才会一次更新上来。怎么解决这个问题呢?只需在increaseb这个指令的实现中,把$digest换成$apply即可。

当调用$digest的时候,只触发当前作用域和它的子作用域上的监控,但是当调用$apply的时候,会触发作用域树上的所有监控。

因此,从性能上讲,如果能确定自己作的这个数据变更所造成的影响范围,应当尽量调用$digest,只有当无法精确知道数据变更造成的影响范围时,才去用$apply,很暴力地遍历整个作用域树,调用其中所有的监控。

从另外一个角度,我们也可以看到,为什么调用外部框架的时候,是推荐放在$apply中,因为只有这个地方才是对所有数据变更都应用的地方,如果用$digest,有可能临时丢失数据变更。

脏检测的利弊
很多人对Angular的脏检测机制感到不屑,推崇基于setter,getter的观测机制,在我看来,这只是同一个事情的不同实现方式,并没有谁完全胜过谁,两者是各有优劣的。

大家都知道,在循环中批量添加DOM元素的时候,会推荐使用DocumentFragment,为什么呢,因为如果每次都对DOM产生变更,它都要修改DOM树的结构,性能影响大,如果我们能先在文档碎片中把DOM结构创建好,然后整体添加到主文档中,这个DOM树的变更就会一次完成,性能会提高很多。

同理,在Angular框架里,考虑到这样的场景:

function TestCtrl($scope) {
  $scope.numOfCheckedItems = 0;
 
  var list = [];
 
  for (var i=0; i<10000; i++) {
    list.push({
      index: i,
      checked: false
    });
  }
 
  $scope.list = list;
 
  $scope.toggleChecked = function(flag) {
    for (var i=0; i<list.length; i++) {
      list[i].checked = flag;
      $scope.numOfCheckedItems++;
    }
  };
}
Copier après la connexion

如果界面上某个文本绑定这个numOfCheckedItems,会怎样?在脏检测的机制下,这个过程毫无压力,一次做完所有数据变更,然后整体应用到界面上。这时候,基于setter的机制就惨了,除非它也是像Angular这样把批量操作延时到一次更新,否则性能会更低。

所以说,两种不同的监控方式,各有其优缺点,最好的办法是了解各自使用方式的差异,考虑出它们性能的差异所在,在不同的业务场景中,避开最容易造成性能瓶颈的用法。

更多深入学习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