浅析$watch ,$apply 和 $digest (Angular篇)

听闻少年二字,当与平庸相斥。这篇文章主要讲述浅析$watch ,$apply 和 $digest (Angular篇)相关的知识,希望能为你提供帮助。
 
前言
了解过angular的人都知道,angular的一大特性就是双向数据绑定。所谓双向数据绑定,即当View中有任何数据发生了变化,其对应的 scope模型会自动地更新,而当scope模型发生变化时,view中的数据也会更新到最新的值。那么它是怎么做到的呢,$watch是怎么工作的,$apply 和 $digest又是用于做什么的,下面我们来探讨一下。
浏览器事件和angular扩展
在标准的浏览器流程中,当事件被触发时(比如点击一个按钮),浏览器会执行该事件的回调函数,执行回调时会进入javascript执行环境。当回调执行完毕,就退出javascript执行环境,然后刷新视图。Angular在此基础上创建了一个自己的执行环境及事件处理循环,只有在AngularJS执行环境中运行的操作,才能享受到AngularJS提供的数据绑定,异常处理,资源管理等功能和服务。Angular和浏览器事件循环交互如下:
 

浅析$watch ,$apply 和 $digest (Angular篇)

文章图片

下面用个例子来解析一下angular执行过程:
html
< button ng-click="changeData()"> 点击< /button> < p> {{data}}< /p>

 
controller
$scope.changeData=https://www.songbingjia.com/android/function(){ $scope.data=1; }

 
(1)上面的“点击”按钮绑定到angular的点击事件,当用户点击按钮时,angular会把changeData函数包装并传入到$scope.$apply(),通过调用$scope.$apply(changeData),进入到angular执行环境,在angular环境中执行changeData。
(2)AngularJS进入$digest循环。这个循环是由两个小循环组成的: $evalAsync队列和$watch列表。执行changeData,changeData中修改了$scope.data;同时,angular遍历整个$watch列表,检测到$watch 列表中的data值的变化,然后再次启动一轮$digest 循环;
(3)直到检测到$watch 列表不再有任何变化后,AngularJS的$digest循环结束,离开AngularJS和Javascript的执行环境。
(4)浏览器把改变的数据data进行重渲染。
$watch
当我们在UI元素中绑定一个$scope对象时,就会往$watch list里面添加一个$watch,对于所有绑定给同一$scope对象的UI元素,也只会添加一个$watch到$watch列表中。看如下代码
(1)
【浅析$watch ,$apply 和 $digest (Angular篇)】html
< input ng-model="name" type="text" placeholder="Your name"> < input ng-model="age"type="text" placeholder="Your age"> < h1> Hello {{name}}< /h1>

这里有两个一样的$scope.name,还有个$scope.pass,所以在$watch list里面加入两个$watch。
(2)
html
< p> {{a}}< /p>

 
controller
$scope.a=\'dataA\'; $scope.a=\'dataB\';

这里虽然在controller中定义了两个$scope对象,但是只有一个$scope.a是绑定到UI元素中的,所以只在$watch list里面加入一个$watch。
 
$digest
当浏览器接收到可以被angular 执行环境处理的事件时(比如ng-click、ng-keypress),$digest循环就会触发。这个循环是由两个小的循环组合起来的。一个处理evalAsync队列,另一个处理$watch队列。
在$digest循环中,angular会遍历完整个$watch列表,所有的$watch都检查完后只要有任何一个$watch的值发生变化,这个循环就会再次触发,继续遍历$watch列表直到检测到不再有任何变化。为什么要再次运行这一循环?因为如果你更新了$watch列表中某个用于更新另一个值的值,Angular将检测不到更新,除非再次运行这个循环。看如下例子
html
< button ng-click="changeNum()"> 点击< /button> < p> {{num1}}< /p> < p> {{num2}}< /p>

 
controller
$scope.num1=1; $scope.num2=$scope.num1+2; $scope.changeNum=function(){ $scope.num1=2; }

当我们点击按钮的时候,改变了num1的数据,由于num2数据是受num1数据影响的,如果不再次启动$digest循环,Angular将检测不到num2数据的更新。
这就是所谓的脏值检测。这样就能够保证每个model都已经不会再变化。记住如果循环超过10次的话,它将会抛出一个异常,防止无限循环。 当$digest循环结束时,DOM相应地变化。
 
$apply
$apply()函数可以使事件进入到Angular的执行环境中执行。那么问题来了,什么时候该用$apply(),什么时候不该用呢?
Angular提供的可用于视图中的任意指令(比如ng-click、ng-keypress)、Angular内置的服务(比如$http、$timeout等),使用的时候都会自动调用$apply()。所以,我们不用再去手动调用$apply()了;
当我们手动处理事件,使用第三方框架(比如jQuery、Facebook API),或者调用setTimeout(),他们并没有调用$apply(),所以事件不能在angular执行环境中执行,$digest循环无法检测到事件中对视图数据的更改,视图无法更新。这个时候,我们就需要手动调用$apply();
 

    推荐阅读