浅谈AngularJs 双向绑定原理(数据绑定机制)
那么什么是双向绑定,下面简单进行讲解。
首先我们要理解数据绑定。我们看到的网站页面中,是由数据和设计两部分组合而成。将设计转换成浏览器能理解的语言,便是html和css主要做的工作。而将数据显示在页面上,并且有一定的交互效果(比如点击等用户操作及对应的页面反应)则是js主要完成的工作。很多时候我们不可能每次更新数据便刷新页面(get请求),而是通过向后端请求相关数据,并通过无刷新加载的方式进行更新页面(post请求)。那么数据进行更新后,页面上相应的位置也能自动做出对应的修改,便是数据绑定。
在以前的开发模式中,这一步一般通过jq操作DOM结构,从而进行更新页面。但这样带来的是大量的代码和大量的操作。如果能在开始的时候,便已经确定好从后端获取的数据到页面上需要进行的操作,当数据发生改变,页面的相关内容也自动发生变化,这样便能极大地方便前端工程师的开发。在新的框架中(angualr,react,vue等),通过对数据的监视,发现变化便根据已经写好的规则进行修改页面,便实现了数据绑定。可以看出,数据绑定是M(model,数据)通过VM(model-view,数据与页面之间的变换规则)向V(view)的一个修改。
而双向绑定则是增加了一条反向的路。在用户操作页面(比如在Input中输入值)的时候,数据能及时发生变化,并且根据数据的变化,页面的另一处也做出对应的修改。有一个常见的例子就是淘宝中的购物车,在商品数量发生变化的时候,商品价格也能及时变化。这样便实现了V——M——VM——V的一个双向绑定。
AngularJs为scope模型上设置了一个监听队列,用来监听数据变化并更新view。每次绑定一个东西到view(html)上时AngularJs就会往$watch队列里插入一条$watch,用来检测它监视的model里是否有变化的东西。当浏览器接收到可以被angularcontext处理的事件时,$digest循环就会触发。$digest会遍历所有的$watch。从而更新DOM。
$watch
这有点类似于我们的观察者模式,在当前作用域$scope下,我们创建一个监控器$watchers和一个监听器$watch,$watchers负责管理所有的$watch,当我们每次绑定到UI上的时候就
controller.js
app.controller('MainCtrl',function($scope){ $scope.Hello="Hello"; $scope.world="World"; });
index.html
{{Hello}}
这里,即便我们在$scope上添加了两个变量,
$digest
当浏览器接收到可以被angularcontext处理的事件时,$digest循环就会触发。$digest将会遍历我们的$watch,如果$watch没有变化,这个循环检测就将停止,如果有至少一个更新过,这个循环就会再次触发,直到所有的$watch都没有变化。这样就能够保证每个model都已经不会再变化。这就是脏检查(DirtyChecking)机制
controller.js
app.controller('MainCtrl',function(){ $scope.name="Foo"; $scope.changeFoo=function(){ $scope.name="Bar"; } });
index.js
{{name}}Changethename
- 当我们按下按钮
- 浏览器接收到一个事件,进入angularcontext。
- $digest循环开始执行,查询每个$watch是否变化。
- 由于监视$scope.name的$watch报告了变化,它会强制再执行一次$digest循环。
- 新的$digest循环没有检测到变化。
- 更新与$scope.name新值相应部分的DOM。
$apply
$apply我们可以直接理解为刷新UI。
app.directive('clickable',function(){ return{ restrict:"E", scope:{ foo:'=' }, template:'{{foo}} ', link:function(scope,element,attrs){ element.bind('click',function(){ scope.foo++; console.log(scope.foo); }); } } });
当我们调用clickable指令的时候,我们可以看到foo的值增加了,但是界面上显示的内容并没有改变。$digest脏检测机制没有触发,检测foo的$watch就没有执行。
$apply()方法的两种形式
1)无参
$scope.$apply();
element.bind('click',function(){ scope.foo++; //iferror scope.$apply(); });
当我们使用这种形式的时候,如果在scope.$apply之前程序发生异常,那scope.$apply没有执行,界面就不会更新
2)有参
$scope.$apply(function(){ ... })
element.bind('click',function(){ scope.$apply(function(){ scope.foo++; }); })
如果用这种形式,即使后面的发生异常,数据还是会更新。
在AngularJS中使用$watch
常用的使用方式:
$scope.name='Hello'; $scope.$watch('name',function(newValue,oldValue){ if(newValue===oldValue){return;} $scope.updated++; });
传入到$watch()中的第二个参数是一个回调函数,该函数在name的值发生变化的时候会被调用。
如果要监听的是一个对象,那还需要第三个参数:
$scope.data.name='Hello'; $scope.$watch('data',function(newValue,oldValue){ if(newValue===oldValue){return;} $scope.updated++; },true);
表示比较的是对象的值而不是引用,如果不加第三个参数true,在data.name变化时,不会触发相应操作,因为引用的是同一引用。
总结
1)只有在$scope变量绑定到页面上,才会创建$watch
2)$apply决定事件是否可以进入angularcontext
3)$digest循环检查model时最少两次,最多10次(多于10次抛出异常,防止无限检查)
4)AngularJs自带的指令已经实现了$apply,所以不需要我们额外的编写
5)在自定义指令时,建议使用带function参数的$apply
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。