#5 watch機制,digest process詳講

watcher是Angular提供的一種對變量的觀察機制,如果存在數(shù)據(jù)的綁定等,則在不同的作用域中都存在相應的 watch list, 這些被觀察的變量將在 digest process (后面會詳講)中得到更新,這也稱之為 臟值檢查。

watch 和 watch listener

$scope (和$rootScope) 對象上存在 $watch 函數(shù),我們可以手動的添加watch listener, 當被觀察的變量被檢測到發(fā)生變化時,變量被更新,然后angularjs自動調(diào)用 watch listener.

// 觀察 'a' 變量的變化
// 后面的回調(diào)函數(shù)就是 watch listener, 2個參數(shù): newValue oldValue
$scope.$watch('a', function(newValue, oldValue) {
  if (newValue != oldValue) {
    $scope.b = $scope.a * 2
  }
})

watches的類型

1.$watch - Reference watch 引用類型的觀測

$watch 是一種引用類型的觀察,只有當引用的地址發(fā)生變化時才觸發(fā),上面的代碼使用原始類型,當值變化時,相應的引用也會發(fā)生變化。下面例子中 obj 是一個對象,只有 obj 引用的地址發(fā)生變化時,才會調(diào)用watch listener, 因此當 a, b, c的值發(fā)生變化時,obj引用的地址并不會發(fā)生改變,因此不會調(diào)用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', 則觀察的類型將變?yōu)橄嗟刃杂^察(也可稱為deep watch)。這樣對象的成員發(fā)生變化時,會自動調(diào)用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 對多個變量同時進行監(jiān)測,這可以說是一種簡便的寫法

$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

這個是對集合元素的觀測:

  • 用于觀測數(shù)組
  • 檢測數(shù)組的變化(比如添加或刪除元素)
  • 不檢測數(shù)組item的變化(比如數(shù)組的元素為對象,對象中的內(nèi)容發(fā)生變化,這并不再監(jiān)察范圍內(nèi), 如果希望監(jiān)察這種變化,可以添加 true, 同$watch)
$scope.emps = [
  { empId: 1001, empName: 'James'},
  { empId: 1002, empName: 'Kobe'},
  { empId: 1003, empName: 'Durant'},
  { empId: 1004, empName: 'Jordan'}
];

// 如果emps數(shù)組增加元素或刪除元素,則會調(diào)用watch listener
// 如果 '$scope.emps[2].empName="Harden"' 并不會調(diào)用watch listener
$scope.$watchCollection('emps', function(newValue, oldValue) {
  //...
})

如果希望 '$scope.emps[2].empName="Harden"' 內(nèi)容的變化調(diào)用watch listener, 則可以添加 true, 變?yōu)?strong>相等性監(jiān)察

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

示例:

// html
<div ng-controller="myCtrl">
  a = {{a}} <input ng-model="a" ng-change="updateC()" />
</div>

// 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' 將調(diào)用 'updateC()'函數(shù)
// 從而改變c的值
// 然后c被檢測到發(fā)生變化,watch listener調(diào)用
// 控制臺顯示 'C updated to 20'

值得注意的是, ng-change 指令的用法, 當a的值發(fā)生變化時,將自動的調(diào)用其綁定的函數(shù)

digest process/loop

通過上面的watch,我們知道,作用域中的可能存在watchers, 這些watcher都存在各個作用域中的 watch list中,如下圖:

![Uploading digest in simple words_926951.jpg . . .]

digest process 簡單的來講就是: 負責遍歷整個watch list,查看變化(也稱之為 dirty-checking(臟值檢查)),有如下特點:

  • 被觀察的變量有變化,則更新變量,如果存在watch listener, 則執(zhí)行watch listener
  • 追蹤變化,告知Angular更新DOM
  • Digest Process 完成之后更新DOM
  • Digest Process 運行在 Angular Context 中,這是angularjs運行時環(huán)境,這個環(huán)境建立在Javascript Context

如下圖:

digest in simple words.jpg

舉個例子: watch list中的 a 變量,檢測到發(fā)生了變化:

  • AngularJS 進行第1次遍歷,發(fā)現(xiàn)a有變化,更新a的值
  • a如果存在watch listener, 則執(zhí)行這個函數(shù)
  • AngularJS再次進行遍歷,查看是否又有變化,如果沒有變化,則Digest process完成,更新DOM;如果又發(fā)現(xiàn)變化,則再次進行遍歷,直到?jīng)]有任何變化

如下圖所示:


digest process - cycle.jpg

從圖中可以看出:

  • AngularJS Digest process 至少遍歷2次, 最多遍歷10次(超過10次拋出錯誤)

我們可以使用 $rootScope.watch的方法來查看初始次數(shù):

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

// 在控制臺中我們可以看到
Digest process start
Digest process start

// 這個過程執(zhí)行了2次

digest process 完整過程

digest process并不會定時的觸發(fā),而是通過下圖中的一些條件引起而觸發(fā):


digest process.jpg

從圖中可以看出,其中幾個觸發(fā)條件為(在AngularJS context中):

  • DOM 事件(ng-click等)
  • Ajax 請求($http等)
  • Timer 回調(diào)($timeout $interval)
  • 瀏覽器地址發(fā)生變化
  • 手動的調(diào)用($apply $digest)

Angularjs 也正是通過上面這些情形當作橋梁和瀏覽器產(chǎn)生關聯(lián)的

和angular不相關的DOM事件,比如 onclick, 并不會觸發(fā)digest process,而 ng-click 則會。

Angular context位于Javascript Context 中,瀏覽器的一些行為會影響到Angular context中的一些觸發(fā)條件,從而觸發(fā)digest process

$apply && $digest

這2個方法用于手動觸發(fā)digest process:

  • 主要用于當作用域變量在Angular Context 之外(比如使用jquery 事件, jquery ajax等)被修改,而 UI 需要刷新數(shù)據(jù)綁定
  • $apply 本質(zhì)上是調(diào)用 $digest
  • 使用方法: $scope.$apply() | $scope.$digest()
apply 和 digest.jpg

$apply

這個啟動digest process的特點:

  • 永遠從 RootScope 開始啟動digest process
  • ng-click, $timeout, $http(ajax) 等操作,背后都會調(diào)用$apply

$digest

這個和$apply的區(qū)別:

  • 當前作用域 (包括子作用域和嵌套作用域) 啟動 digest process
  • 不從RootScope或父作用域啟動
  • 也能夠從RootScope開始東東digest process(這就相當于$apply了)

示例:

// html
<div ng-controller="MyCtrl" id="sum">
  a: {{a}} <input ng-model="a" />
  b: {} <input ng-model="b" />
  <hr />
  <button ng-click="getSum()">ng-click求和</button> // 這里使用ng-click,自動觸發(fā)digest process
  <button ng-click="btnClick()">外部事件求和</button> // 這里使用外部DOM事件求和, 需要手動的觸發(fā)digest process
  <hr />
  總和為: {{s}}
</div>

// 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() 也可以添加一個函數(shù),上面可以寫為:

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

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

總結(jié)

這一章主要有如下內(nèi)容:

  • AngularJS的監(jiān)察機制:watch list, watch 的分類: $watch, $watchGroup, $watchCollection
  • digest process的機制
  • ng-change 指令,當綁定的變量發(fā)生變化時, 調(diào)用相應的函數(shù)
  • Angular Context 的概念
  • 觸發(fā)digest process的條件
  • 手動觸發(fā)digest process: $apply(), $digest(), 兩者之間的差異主要在于digest process開始的地方: $apply()從rootScope開始,而$digest()從當前scope開始,這對某些情況是有差別的
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容