#5 watch机制,digest process详讲

watcher是Angular提供的一种对变量的观察机制,如果存在数据的绑定等,则在不同的作用域中都存在相应的 watch list, 这些被观察的变量将在 digest process (后面会详讲)中得到更新,这也称之为 脏值检查

watch 和 watch listener

$scope (和$rootScope) 对象上存在 $watch 函数,我们可以手动的添加watch listener, 当被观察的变量被检测到发生变化时,变量被更新,然后angularjs自动调用 watch listener.

// 观察 'a' 变量的变化
// 后面的回调函数就是 watch listener, 2个参数: newValue oldValue
$scope.$watch('a', function(newValue, oldValue) {
  if (newValue != oldValue) {
    $scope.b = $scope.a * 2
  }
})

watches的类型

1.$watch - Reference watch 引用类型的观测

$watch 是一种引用类型的观察,只有当引用的地址发生变化时才触发,上面的代码使用原始类型,当值变化时,相应的引用也会发生变化。下面例子中 obj 是一个对象,只有 obj 引用的地址发生变化时,才会调用watch listener, 因此当 a, b, c的值发生变化时,obj引用的地址并不会发生改变,因此不会调用watch listener,这一点要注意:

$scope.obj = {
  a: 1,
  b: 2,
  c: 4
}
// 引用观察
$scope.$watch('obj', function(newValue, oldValue) {
  if ($newValue != oldValue) {
    $scope.obj.c = $scope.obj.a * $scope.obj.b; 
  }
});

$watch with 'true' - Equality watch

同上面的例子,如果后面再添加一个 'true', 则观察的类型将变为相等性观察(也可称为deep watch)。这样对象的成员发生变化时,会自动调用watch listener

$scope.obj = {
  a: 1,
  b: 2,
  c: 4
}
// 相等性观察
$scope.$watch('obj', function(newValue, oldValue) {
  if ($newValue != oldValue) {
    $scope.obj.c = $scope.obj.a * $scope.obj.b; 
  }
}, true);

2.$watchGroup - Reference watch for multiple variables

$watchGroup 对多个变量同时进行监测,这可以说是一种简便的写法

$scope.a =1;
$scope.b = 2;
$scope.c = 4;

// 相等性观察
$scope.$watchGroup(['a', 'b'], function(newValue, oldValue) {
  if ($newValue != oldValue) {
    $scope.obj.c = $scope.obj.a * $scope.obj.b; 
  }
});

3.$watchCollection - Collection Watch

这个是对集合元素的观测:

  • 用于观测数组
  • 检测数组的变化(比如添加或删除元素)
  • 不检测数组item的变化(比如数组的元素为对象,对象中的内容发生变化,这并不再监察范围内, 如果希望监察这种变化,可以添加 true, 同$watch)
$scope.emps = [
  { empId: 1001, empName: 'James'},
  { empId: 1002, empName: 'Kobe'},
  { empId: 1003, empName: 'Durant'},
  { empId: 1004, empName: 'Jordan'}
];

// 如果emps数组增加元素或删除元素,则会调用watch listener
// 如果 '$scope.emps[2].empName="Harden"' 并不会调用watch listener
$scope.$watchCollection('emps', function(newValue, oldValue) {
  //...
})

如果希望 '$scope.emps[2].empName="Harden"' 内容的变化调用watch listener, 则可以添加 true, 变为相等性监察

$scope.$watchCollection('emps', function(newValue, oldValue) {
  //...
}, true)

示例:

// html
a = {{a}}
// js app.controller('myCtrl', function($scope) { $scope.a = 10; $scope.c = 1; $scope.updateC = function() { $scope.c = $scope.a * 2 } $scope.watch('c', function(newValue, oldValue) { if (newValue != oldValue) { console.log('C updated to ' + newValue) } }) }) // 当我们改变 'a' 的值的时候,比如10, 'ng-change' 将调用 'updateC()'函数 // 从而改变c的值 // 然后c被检测到发生变化,watch listener调用 // 控制台显示 'C updated to 20'

值得注意的是, ng-change 指令的用法, 当a的值发生变化时,将自动的调用其绑定的函数

digest process/loop

通过上面的watch,我们知道,作用域中的可能存在watchers, 这些watcher都存在各个作用域中的 watch list中,如下图:

#5 watch机制,digest process详讲_第1张图片
![Uploading digest in simple words_926951.jpg . . .]

digest process 简单的来讲就是: 负责遍历整个watch list,查看变化(也称之为 dirty-checking(脏值检查)),有如下特点:

  • 被观察的变量有变化,则更新变量,如果存在watch listener, 则执行watch listener
  • 追踪变化,告知Angular更新DOM
  • Digest Process 完成之后更新DOM
  • Digest Process 运行在 Angular Context 中,这是angularjs运行时环境,这个环境建立在Javascript Context

如下图:

#5 watch机制,digest process详讲_第2张图片
digest in simple words.jpg

举个例子: watch list中的 a 变量,检测到发生了变化:

  • AngularJS 进行第1次遍历,发现a有变化,更新a的值
  • a如果存在watch listener, 则执行这个函数
  • AngularJS再次进行遍历,查看是否又有变化,如果没有变化,则Digest process完成,更新DOM;如果又发现变化,则再次进行遍历,直到没有任何变化

如下图所示:


#5 watch机制,digest process详讲_第3张图片
digest process - cycle.jpg

从图中可以看出:

  • AngularJS Digest process 至少遍历2次, 最多遍历10次(超过10次抛出错误)

我们可以使用 $rootScope.watch的方法来查看初始次数:

$rootScope.watch(function() {
  console.log('Digest process start');
})

// 在控制台中我们可以看到
Digest process start
Digest process start

// 这个过程执行了2次

digest process 完整过程

digest process并不会定时的触发,而是通过下图中的一些条件引起而触发:


#5 watch机制,digest process详讲_第4张图片
digest process.jpg

从图中可以看出,其中几个触发条件为(在AngularJS context中):

  • DOM 事件(ng-click等)
  • Ajax 请求($http等)
  • Timer 回调($timeout $interval)
  • 浏览器地址发生变化
  • 手动的调用($apply $digest)

Angularjs 也正是通过上面这些情形当作桥梁和浏览器产生关联的

和angular不相关的DOM事件,比如 onclick, 并不会触发digest process,而 ng-click 则会。

Angular context位于Javascript Context 中,浏览器的一些行为会影响到Angular context中的一些触发条件,从而触发digest process

$apply && $digest

这2个方法用于手动触发digest process:

  • 主要用于当作用域变量在Angular Context 之外(比如使用jquery 事件, jquery ajax等)被修改,而 UI 需要刷新数据绑定
  • $apply 本质上是调用 $digest
  • 使用方法: $scope.$apply() | $scope.$digest()
#5 watch机制,digest process详讲_第5张图片
apply 和 digest.jpg

$apply

这个启动digest process的特点:

  • 永远从 RootScope 开始启动digest process
  • ng-click, $timeout, $http(ajax) 等操作,背后都会调用$apply

$digest

这个和$apply的区别:

  • 当前作用域 (包括子作用域和嵌套作用域) 启动 digest process
  • 不从RootScope或父作用域启动
  • 也能够从RootScope开始东东digest process(这就相当于$apply了)

示例:

// html
a: {{a}} b: {{b}}
// 这里使用ng-click,自动触发digest process // 这里使用外部DOM事件求和, 需要手动的触发digest process
总和为: {{s}}
// js app.controller('MyCtrl', function($scope) { $scope.a = 10; $scope.b = 10; $scope.s = 0; $scope.getSum = function() { $scope.s = parseInt($scope.a, 10) + parseInt($scope.b, 10); } }) // Angular context 外部逻辑 var btnClick = function() { var sumDiv = document.getElementById('sum'); // 找出该作用域 这里的 '$scope' 可以为任何值 var $scope = angular.element(sumDiv).scope(); $scope.s = parseInt($scope.a, 10) + parseInt($scope.b, 10); }

仅仅这样写,点击 'ng-click求和' 将得到正确的值,但是点击 '外部事件求和' 按钮,虽然s的值会更新,但是DOM并未更新,这是我们需要使用 $scope.$apply() 手动的更新

var btnClick = function() {
  var sumDiv = document.getElementById('sum');
  var $scope = angular.element(sumDiv).scope();
  $scope.s = parseInt($scope.a, 10) + parseInt($scope.b, 10);

  $scope.$apply(); // 手动的启动digest process
} 

$scope.$apply() 也可以添加一个函数,上面可以写为:

var btnClick = function() {
  var sumDiv = document.getElementById('sum');
  var $scope = angular.element(sumDiv).scope();

  // 使用函数的形式
  $scope.$apply(function() {
    $scope.s = parseInt($scope.a, 10) + parseInt($scope.b, 10);
  });
} 

总结

这一章主要有如下内容:

  • AngularJS的监察机制:watch list, watch 的分类: $watch, $watchGroup, $watchCollection
  • digest process的机制
  • ng-change 指令,当绑定的变量发生变化时, 调用相应的函数
  • Angular Context 的概念
  • 触发digest process的条件
  • 手动触发digest process: $apply(), $digest(), 两者之间的差异主要在于digest process开始的地方: $apply()从rootScope开始,而$digest()从当前scope开始,这对某些情况是有差别的

你可能感兴趣的:(#5 watch机制,digest process详讲)