1、什么是双向数据绑定
双向数据绑定:
Angular实现了双向绑定机制。所谓的双向绑定,无非是从界面的操作能实时反映到数据,数据的变更能实时展现到界面。即数据模型(Module)和视图(View)之间的双向绑定。
例子:
<!DOCTYPE html> <html> <head> <Meta charset="utf-8"> <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script> </head> <body> <div ng-app="myApp" ng-controller="myCtrl"> 输入: <input ng-model="name"> <h1>你输入了: {{name}}</h1> </div> <script> var app = angular.module('myApp',[]); app.controller('myCtrl',function($scope) { $scope.name = "test"; }); </script> </body> </html>
这里我使用了{{}}来实现了最简单数据绑定,{{}}进行的数据绑定时单向的,只实现了数据展示。然后我用input标签配合ng-model 实现了一个简单的双向数据绑定的例子,双向绑定最常用的场景就是表单,这样当用户在前端页面完成输入后,不用任何操作,我们就已经拿到了用户的数据存放到数据模型中了。
2、双向数据绑定与单向数据绑定
单向数据绑定:
数据模型–>视图
El表达式中常见 ${变量名}以及{{}} ,它只提供从数据源到视图的单方向的数据展示。
缺点:HTML代码一旦生成完以后,就没有办法再变了,如果有新的数据来了,那就必须把之前的HTML代码去掉,重新整合生成一次。
双向数据绑定:
数据模型<==>视图
用户在视图上的修改会自动同步到数据模型中去,同样的,如果数据模型中的值发生了变化,也会立刻同步到视图中去。
缺点:双向数据绑定的应用场景非常有限。
3、双向数据绑定原理
带着问题看下面的代码。
问题:绑定数据是怎么生效的?
如图示例,当点击+1按钮时,显示的数据是否会正常的增加?
如果会正常增加,说明原因。
如果不会,说明原因并修改代码使其可以正常显示。
<!DOCTYPE html> <html> <head> <Meta charset="utf-8"> <script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script> </head> <body ng-app="test"> <div ng-controller="CounterCtrl"> <br> <button myclick>+1</button> <br> {{counter}} </div> <script> 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; }); </script> </body> </html>
原因从脏检查机制开始说起。
脏检查机制:Angular将双向绑定转换为一堆watch表达式,然后递归这些表达式检查是否发生过变化,如果变了则执行相应的watcher函数(指view上的指令,如ng-bind,ng-show等或是{{}})。等到model中的值不再发生变化,也就不会再有watcher被触发,一个完整的digest循环就完成了。
脏检查机制的触发:Angular中在view上声明的事件指令,如:ng-click、ng-change等,会将浏览器的事件转发给$scope上相应的model的响应函数。等待相应函数改变model,紧接着触发脏检查机制刷新view。
所以,上文中的代码无法实现相应功能的原因就是缺乏触发Angular脏检查机制的条件,而手动添加了scope.$digest()使其执行了脏检查机制更新了view。
watch表达式:可以是一个函数、可以是$scope上的一个属性名,也可以是一个字符串形式的表达式。$watch函数所监听的对象叫做watch表达式。
watcher函数:指在view上的指令(ngBind,ngShow、ngHide等)以及{{}}表达式,他们所注册的函数。每一个watcher对象都包括:监听函数,上次变化的值,获取监听表达式的方法以及监听表达式,最后还包括是否需要使用深度对比(angular.equals())。
脏检查机制(dirty-checking)是实现双向数据绑定的重要基础。
Angular中的$digest函数:当接受view上的事件指令转发的事件时,就会切换到Angular的上下文环境,来影响这类事件,$digest循环就会触发。
$digest函数的工作过程:
遍历一遍所有的watcher函数就是一轮脏检查,执行完一轮之后,只要有watcher监听的值改变过,那么就会重新在进行一轮,直到所有的值都没有变化。从第一轮到所有数据稳定称为一个完整的$digest循环。当循环结束后,才把模型的变化结果更新到dom中去,防止频繁的dom操作。
我们已经知道什么时候以及怎么开始digest循环了,那么digest循环具体做了些什么?
在digest循环中,AngularJS会遍历整个$watch列表,所有watcher都会被触发,当一个wathcer被触发时,AngularJS会检测Scope模型相应的数据,如果它发生了变化,那么关联到该watcher的回调函数就会被触发。
如果执行了一次digest循环后某个值发生了变化,那么AngularJS会再次循环,直至不再有任何变化。这是因为你在$watch中更新某个值,如果该值对应的$watch已在这遍循环通过,AngularJS将检测不到变化无法更新。如果循环运行了10次或更多次,AngularJS会抛出异常并停止。(就算没有更新值,AngularJS也会多运行一次来确保没有改变,也就是至少运行两次)。
以上就是Angular的数据双向绑定的原理。 参考资料:《Angular深度剖析与最佳实践》