單向綁定(ng-bind) 和 雙向綁定(ng-model) 的區別
ng-bind 單向數據綁定($scope -> view),用於數據顯示,簡寫形式是 {{}}。
1 |
<span ng-bind="val"></span> |
兩者的區別在於頁面沒有加載完畢 {{val}} 會直接顯示到頁面,直到 Angular 渲染該綁定數據(這種行為有可能將 {{val}} 讓用戶看到);而 ng-bind 則是在 Angular 渲染完畢后將數據顯示。
ng-model 是雙向數據綁定($scope -> view and view -> $scope),用於綁定值會變化的表單元素等。
1 |
<input type="text" ng-model="val" /> |
雙向數據綁定的原理
雙向數據綁定意味着當 view 中有任何數據發生變化會自動地反饋到 scope 的數據上,當 scope模型發生變化時,view 中的數據也會更新到最新的值。很顯然,這需要一個監控。
事實上,AngularJS 確實在幕后為 scope 模型上設置了一個 監聽隊列,用來監聽數據變化並更新view 。
每次綁定一個東西到 view 上時 AngularJS 就會往 $watch 隊列里插入一條 $watch,用來檢測它監視的 model 里是否有變化的東西。
當瀏覽器接收到可以被 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 發生了變化。
這就是臟檢查(Dirty Checking),它用來處理在 listener 函數被執行時可能引起的 model變化。因此 $digest 循環會持續運行直到 model 不再發生變化,或者 $digest 循環的次數達到了 10 次(超過 10 次后拋出一個異常,防止無限循環)。
當 $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 |
$scope.setMsg = function() { |
運行這個例子,會看到過了兩秒鍾之后,控制台確實會顯示出已經更新的 model,然而,view 並沒有更新。
在 $scope.getMessage 加入 $apply() 方法。
1 |
$scope.getMessage = function() { |
再運行就 OK 了。
不過,在 AngularJS 中應該盡量使用 $timeout Service 來代替 setTimeout(),因為前者會幫你調用 $apply(),讓你不需要手動地調用它。
1 |
$timeout(function(){ |
場景二
實現一個 click 的指令,類似以下功能:
1 |
<button ng-click="val=val+1">increase 1</button> |
directive 的編寫如下:
1 |
app.directive("inc", function() { |
跟場景一的結果一樣,這個時候,點擊按鈕,界面上的數字並不會增加。但查看調試器,發現數據確實已經增加了。
在 scope.val++; 一行后面添加 scope.$apply(); 或者 scope.$digest(); 就 OK 了。
$apply() 方法的兩種形式
1) 無參
1 |
$scope.$apply() |
2) 有參
1 |
$scope.$apply(function(){ |
應該總使用接受一個 function 作為參數的 $apply() 方法。這是因為當傳入一個 function 到$apply() 中的時候,這個 function 會被包裝到一個 try…catch 塊中,所以一旦有異常發生,該異常會被 $exceptionHandler service 處理。
想象一下如果有個 alert 框顯示錯誤給用戶,然后有個第三方的庫進行一個網絡調用然后失敗了,如果不把它封裝進 $apply 里面,Angular 永遠不會知道失敗了,alert 框就永遠不會彈出來了。
在 AngularJS 中使用 $watch
常用的使用方式:
1 |
$scope.name = 'htf'; |
傳入到 $watch() 中的第二個參數是一個回調函數,該函數在 name 的值發生變化的時候會被調用。
如果要監聽的是一個對象,那還需要第三個參數:
1 |
$scope.data.name = 'htf'; |
表示比較的是對象的值而不是引用,如果不加第三個參數 true ,在 data.name 變化時,不會觸發相應操作,因為引用的是同一引用。
參考
- 理解$watch ,$apply 和 $digest —- 理解數據綁定過程
- 理解Angular中的$apply()以及$digest()
- Angular沉思錄(一)數據綁定
- 構建自己的AngularJS,第一部分:Scope和Digest
轉:http://huangtengfei.com/2015/09/data-bind-of-angularjs/
