watcher是Angular提供的一種對變量的觀察機制哺徊,如果存在數(shù)據(jù)的綁定等驰弄,則在不同的作用域中都存在相應(yīng)的 watch list
, 這些被觀察的變量將在 digest process
(后面會詳講)中得到更新,這也稱之為 臟值檢查绣的。
watch 和 watch listener
在 $scope
(和$rootScope
) 對象上存在 $watch
函數(shù)叠赐,我們可以手動的添加watch listener, 當(dāng)被觀察的變量被檢測到發(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
是一種引用類型的觀察筑煮,只有當(dāng)引用的地址發(fā)生變化時才觸發(fā)妇蛀,上面的代碼使用原始類型,當(dāng)值變化時,相應(yīng)的引用也會發(fā)生變化桑谍。下面例子中 obj
是一個對象运挫,只有 obj
引用的地址發(fā)生變化時鹃唯,才會調(diào)用watch listener, 因此當(dāng) 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)
}
})
})
// 當(dāng)我們改變 'a' 的值的時候蔬胯,比如10, 'ng-change' 將調(diào)用 'updateC()'函數(shù)
// 從而改變c的值
// 然后c被檢測到發(fā)生變化,watch listener調(diào)用
// 控制臺顯示 'C updated to 20'
值得注意的是位他, ng-change
指令的用法氛濒, 當(dāng)a的值發(fā)生變化時产场,將自動的調(diào)用其綁定的函數(shù)
digest process/loop
通過上面的watch,我們知道舞竿,作用域中的可能存在watchers, 這些watcher都存在各個作用域中的 watch list
中京景,如下圖:
而 digest process 簡單的來講就是: 負(fù)責(zé)遍歷整個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
上
如下圖:
舉個例子: 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)]有任何變化
如下圖所示:
從圖中可以看出:
- 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ā):
從圖中可以看出躺率,其中幾個觸發(fā)條件為(在AngularJS context中):
- DOM 事件(ng-click等)
- Ajax 請求($http等)
- Timer 回調(diào)($timeout $interval)
- 瀏覽器地址發(fā)生變化
- 手動的調(diào)用(
$apply
$digest
)
Angularjs 也正是通過上面這些情形當(dāng)作橋梁和瀏覽器產(chǎn)生關(guān)聯(lián)的
和angular不相關(guān)的DOM事件玛界,比如 onclick, 并不會觸發(fā)digest process,而 ng-click 則會。
Angular context位于Javascript Context 中悼吱,瀏覽器的一些行為會影響到Angular context中的一些觸發(fā)條件慎框,從而觸發(fā)digest process
$apply && $digest
這2個方法用于手動觸發(fā)digest process:
- 主要用于當(dāng)作用域變量在Angular Context 之外(比如使用jquery 事件, jquery ajax等)被修改后添,而 UI 需要刷新數(shù)據(jù)綁定
-
$apply
本質(zhì)上是調(diào)用$digest
- 使用方法:
$scope.$apply()
|$scope.$digest()
$apply
這個啟動digest process的特點:
- 永遠(yuǎn)從
RootScope
開始啟動digest process - ng-click, $timeout, $http(ajax) 等操作笨枯,背后都會調(diào)用$apply
$digest
這個和$apply的區(qū)別:
- 從 當(dāng)前作用域 (包括子作用域和嵌套作用域) 啟動 digest process
- 不從RootScope或父作用域啟動
- 也能夠從RootScope開始東東digest process(這就相當(dāng)于$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 指令压彭,當(dāng)綁定的變量發(fā)生變化時睦优, 調(diào)用相應(yīng)的函數(shù)
- Angular Context 的概念
- 觸發(fā)digest process的條件
- 手動觸發(fā)digest process:
$apply()
,$digest()
, 兩者之間的差異主要在于digest process開始的地方:$apply()
從rootScope開始,而$digest()
從當(dāng)前scope開始哮塞,這對某些情況是有差別的