單向綁定(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/