AngularJS 的數據綁定

單向綁定(ng-bind) 和 雙向綁定(ng-model) 的區別

ng-bind 單向數據綁定($scope -> view),用於數據顯示,簡寫形式是 {{}}git

1
<span ng-bind="val"></span>

二者的區別在於頁面沒有加載完畢 {{val}} 會直接顯示到頁面,直到 Angular 渲染該綁定數據(這種行爲有可能將 {{val}} 讓用戶看到);而 ng-bind 則是在 Angular 渲染完畢後將數據顯示。angularjs

ng-model 是雙向數據綁定($scope -> view and view -> $scope),用於綁定值會變化的表單元素等。github

1
<input type="text" ng-model="val" />

雙向數據綁定的原理

雙向數據綁定意味着當 view 中有任何數據發生變化會自動地反饋到 scope 的數據上,當 scope模型發生變化時,view 中的數據也會更新到最新的值。很顯然,這須要一個監控。瀏覽器

事實上,AngularJS 確實在幕後爲 scope 模型上設置了一個 監聽隊列,用來監聽數據變化並更新view 。網絡

每次綁定一個東西到 view 上時 AngularJS 就會往 $watch 隊列裏插入一條 $watch,用來檢測它監視的 model 裏是否有變化的東西。app

當瀏覽器接收到能夠被 angular context 處理的事件時,$digest 循環就會觸發。$digest會遍歷全部的 $watch 。框架

一次更新的操做(至少觸發兩次 $digest() 循環)

好比進行一次 click 操做:函數

1
<button ng-click="val=val+1">increase 1</button>
  • 按下按鈕
  • 瀏覽器接收到一個事件,進入 angular context
  • $digest 循環開始執行,查詢每一個 $watch 是否變化
  • 因爲監視 $scope.val 的 $watch 報告了變化,它會強制再執行一次 $digest 循環。
  • 新的 $digest 循環沒有檢測到變化。
  • 瀏覽器拿回控制權,更新與 $scope.val 新值相應部分的 DOM 。

$digest 循環會運行多少次?

$digest 循環不會只運行一次。在當前的一次循環結束後,它會再執行一次循環用來檢查是否有models 發生了變化。ui

這就是髒檢查(Dirty Checking),它用來處理在 listener 函數被執行時可能引發的 model變化。所以 $digest 循環會持續運行直到 model 再也不發生變化,或者 $digest 循環的次數達到了 10 次(超過 10 次後拋出一個異常,防止無限循環)。spa

當 $digest 循環結束時,DOM 相應地變化。

$apply() 和 $digest() 的區別

$apply 是 $scope(或者是 direcvie 裏的 link 函數中的 scope)的一個函數,調用它會強制一次 $digest 循環(除非當前正在執行循環,這種狀況下會拋出一個異常,這是咱們不須要在那裏執行 $apply 的標誌)。

$apply() 和 $digest() 有兩個區別。

1) 最直接的差別是, $apply 能夠帶參數,它能夠接受一個函數,而後在應用數據以後,調用這個函數。因此,通常在集成非 Angular 框架(好比jQuery)的代碼時,能夠把代碼寫在這個裏面調用。

2) 當調用 $digest 的時候,只觸發當前做用域和它的子做用域上的監控,可是當調用 $apply的時候,會觸發做用域樹上的全部監控。

何時手動調用 $apply() 方法?

取決因而否在 Angular 上下文環境(angular context)。

典型的須要調用 $apply() 方法的場景是:

1) 使用了 JavaScript 中的 setTimeout() 來更新一個 scope model

2) 用指令設置一個 DOM 事件 listener 而且在該 listener 中修改了一些 models

場景一

1
2
3
4
5
6
7
$scope.setMsg = function() { 
setTimeout(function() {
$scope.message = 'hello world';
console.log('message:' + $scope.message);
}, 2000);
}
$scope.setMsg();

運行這個例子,會看到過了兩秒鐘以後,控制檯確實會顯示出已經更新的 model,然而,view 並無更新。

在 $scope.getMessage 加入 $apply() 方法。

1
2
3
4
5
6
7
8
$scope.getMessage = function() { 
setTimeout(function() {
$scope.$apply(function() {
$scope.message = 'hello world';
console.log('message:' + $scope.message);
});
}, 2000);
}

再運行就 OK 了。

不過,在 AngularJS 中應該儘可能使用 $timeout Service 來代替 setTimeout(),由於前者會幫你調用 $apply(),讓你不須要手動地調用它。

1
2
3
4
$timeout(function(){
$scope.message = 'hello world';
console.log('message:' + $scope.message);
}, 2000)

場景二

實現一個 click 的指令,相似如下功能:

1
<button ng-click="val=val+1">increase 1</button>

directive 的編寫以下:

1
2
3
4
5
6
7
app.directive("inc", function() {
return function (scope, element, attr) {
element.on("click", function() {
scope.val++;
});
};
});

跟場景一的結果同樣,這個時候,點擊按鈕,界面上的數字並不會增長。但查看調試器,發現數據確實已經增長了。

在 scope.val++; 一行後面添加 scope.$apply(); 或者 scope.$digest(); 就 OK 了。

$apply() 方法的兩種形式

1) 無參

1
$scope.$apply()

2) 有參

1
2
3
$scope.$apply(function(){
...
})

應該總使用接受一個 function 做爲參數的 $apply() 方法。這是由於當傳入一個 function 到$apply() 中的時候,這個 function 會被包裝到一個 try…catch 塊中,因此一旦有異常發生,該異常會被 $exceptionHandler service 處理。

想象一下若是有個 alert 框顯示錯誤給用戶,而後有個第三方的庫進行一個網絡調用而後失敗了,若是不把它封裝進 $apply 裏面,Angular 永遠不會知道失敗了,alert 框就永遠不會彈出來了。

在 AngularJS 中使用 $watch

經常使用的使用方式:

1
2
3
4
5
$scope.name = 'htf';
$scope.$watch('name', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
});

傳入到 $watch() 中的第二個參數是一個回調函數,該函數在 name 的值發生變化的時候會被調用。

若是要監聽的是一個對象,那還須要第三個參數:

1
2
3
4
5
$scope.data.name = 'htf';
$scope.$watch('data', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
}, true);

表示比較的是對象的值而不是引用,若是不加第三個參數 true ,在 data.name 變化時,不會觸發相應操做,由於引用的是同一引用。

參考

  1. 理解$watch ,$apply 和 $digest —- 理解數據綁定過程
  2. 理解Angular中的$apply()以及$digest()
  3. Angular沉思錄(一)數據綁定
  4. 構建本身的AngularJS,第一部分:Scope和Digest

轉:http://huangtengfei.com/2015/09/data-bind-of-angularjs/

相關文章
相關標籤/搜索