digest循环和$apply

digest循环和$apply_第1张图片

让我们来看看Angular在后台是如何工作的。如何只使用几行代码就得到神奇的数据绑定?最重要的是理解$digest循环是如何工作的,以及如何使用$apply()方法。

在标准的浏览器流程中,当事件被触发时(比如点击一个链接)。浏览器会执行注册给该事件的回调函数。

页面加载、$http请求返回响应、鼠标移动以及按钮被点击等情况都会触发事件。

当事件被触发时,JavaScript就会创建一个事件对象,并执行这个事件对象所在的监听特定事件的所有函数。然后它会运行JavaScript函数内的回调方法,这会回到浏览器中,还可能更新DOM。

注意:同一时间不能运行两个事件。浏览器会等待前一个事件处理程序执行完成,再调用下一个事件处理程序。

 

在非Angular JavaScript环境中,可以给div的点击事件附加一个回调函数。无论何时,只要发现元素上的点击事件,这个回调函数就会运行;

var div = document.getElementById("clickDiv");

div.addEventListener("click",function(evt){

      console.log("evt",evt);

})

1.1 $watch 列表

html>
<html lang="zh_CN" data-ng-app="app">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
    
    <script src="vendor/angular/angular.js">script>

head>

<body >
 
  
    <input type="text" class="form-control" id="orgName" ng-model="orgName">
 
 
     

Hello {{name}}

body>
html>

 

无路何时,只要用户更新这个输入字段,UI中的{{name}}就会改变。发生这一变化是因为我们把UI中的输入字段绑定给了scope.name属性。为了更新这个视图,Angular需要追踪变化。它是通过给$watch列表添加一个监控函数做到这一点的。

$scope对象上的属性只会在其被用于视图时绑定。在上述例子中,我们只给$watch列表添加了一个函数。

记住,对于所有绑定给同一$scope对象的UI元素,只会添加一个$watch到$watch列表中。

这个$watch列表会在$digest循环中通过一个叫做“脏值检查”的程序解析。
 

其次就是什么时候去脏检查

angular在我们所写的绝大部分代码中都会触发比较事件。比如:controller 初始化的时候,ng-click事件和所有以ng-开头的事件执行后,$http 回调完成后,都会触发脏检查。

当然,触发脏检查的点是在函数执行完之后,但不表明异步调用也执行完成,所以,如果我们的功能是异步的,那你会发现我们的改变并没有更新到dom上。   

来个demo?好,请看:

function Ctrl($scope) {
    $scope.message = "Waiting 2000ms for update";                                                                                    
    setTimeout(function () {
        $scope.message = "Timeout called!";
        // AngularJS unaware of update to $scope
    }, 2000);
}

dom上显示的message是 “Waiting 2000ms for update,永远都不是 “Timeout called!”

这就是 $apply的应用场景,使用$scope.$apply()手动触发脏检查。其实angular还贴心的提供了一个$timeout,那 $timeout和setTimeout在功能上有什么区别吗?可以说没有,唯一的区别就是,使用$timeout异步完成之后,angular会自动触发$apply()

如果已经在一个具有$apply环境中,调用$apply()会抛出异常, 有空你可以试一试。

$apply()接受一个function的参数,function中被绑定的对象会被脏检查,function不能是异步的哦,这个很好理解,如果让你去实现这个apply函数,$apply()不带参数时,会将当前作用域的所有监听对象都脏检查一遍,所以不带参数是有害的, 必然会做很多无用的脏检查,浪费性能。

再次就是如何脏检查

脏检查只要是由 $digest() 完成,$apply()被调用之后,最终会触发 $digest() ,

$digest 主要代码如下:

if ((watchers = current.$$watchers)) {
// process our watches
length = watchers.length;
while (length--) {
   try {
      watch = watchers[length];
      // Most common watches are on primitives, in which case we can short
      // circuit it with === operator, only when === fails do we use .equals
      if (watch && (value = watch.get(current)) !==(last = watch.last) &&  !(watch.eq ? equals(value, last) : (typeof value ==number' && typeof last == 'number'
                         && isNaN(value) && isNaN(last)))) {

                dirty = true;
                watch.last = watch.eq ? copy(value) : value;
                watch.fn(value, ((last === initWatchVal) ? value : last), current);
                if (ttl < 5) {
                  logIdx = 4 - ttl;
                  if (!watchLog[logIdx]) watchLog[logIdx] = [];
                  logMsg = (isFunction(watch.exp))
                      ? 'fn: ' + (watch.exp.name || watch.exp.toString())
                      : watch.exp;
                  logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
                  watchLog[logIdx].push(logMsg);
                }
              }


            } catch (e) {
              $exceptionHandler(e);
            }
          }

遍历watchers,比较监视的属性是否变化.

 

 

 

你可能感兴趣的:(digest循环和$apply)