#5 watch機制瓷翻,digest process詳講

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中京景,如下圖:

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

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

如下圖:

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 也正是通過上面這些情形當(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.jpg

$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開始哮塞,這對某些情況是有差別的
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刨秆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子忆畅,更是在濱河造成了極大的恐慌衡未,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件家凯,死亡現(xiàn)場離奇詭異缓醋,居然都是意外死亡,警方通過查閱死者的電腦和手機绊诲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門送粱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掂之,你說我怎么就攤上這事抗俄。” “怎么了世舰?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵动雹,是天一觀的道長。 經(jīng)常有香客問我跟压,道長胰蝠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任震蒋,我火速辦了婚禮茸塞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘查剖。我一直安慰自己钾虐,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布笋庄。 她就那樣靜靜地躺著禾唁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪无切。 梳的紋絲不亂的頭發(fā)上荡短,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機與錄音哆键,去河邊找鬼掘托。 笑死,一個胖子當(dāng)著我的面吹牛籍嘹,可吹牛的內(nèi)容都是我干的闪盔。 我是一名探鬼主播弯院,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼泪掀!你這毒婦竟也來了听绳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤异赫,失蹤者是張志新(化名)和其女友劉穎椅挣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體塔拳,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡鼠证,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了靠抑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片量九。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖颂碧,靈堂內(nèi)的尸體忽然破棺而出荠列,到底是詐尸還是另有隱情,我是刑警寧澤载城,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布弯予,位于F島的核電站,受9級特大地震影響个曙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜受楼,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一垦搬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧艳汽,春花似錦猴贰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至馋艺,卻和暖如春栅干,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捐祠。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工碱鳞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人踱蛀。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓窿给,卻偏偏與公主長得像贵白,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子崩泡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容