最近專案上使用了比較多的angular JS,一直都對它感覺比較陌生,總覺得有點反直覺,這段時間,準備下定決心弄明白,這個框架到底是怎麼一回事,以及它的工作原理,生命週期……一點一點的啃完它吧。首先,讓我們先來看看$watch、$digest、$apply這三個方法吧!
- $watch(watchExpression, listener, objectEquality)
Param | Type | Details |
watchExpression |
function() string |
Expression that is evaluated on each $digest cycle. A change in the return value triggers a call to the listener.
|
listener (optional) |
function() string |
Callback called whenever the return value of the watchExpressionchanges.
|
objectEquality (optional) |
boolean | Compare object for equality rather than for reference. |
從表格中可以看到,watchExpression和listener可以是一個string,也可以是一個function(scope)。該表示式在每次呼叫了$digest方法之後都會重新算值,如果返回值發生了改變,listener就會執行。在判斷newValue和oldValue是否相等時,會遞迴的呼叫angular.equals方法。在儲存值以備後用的時候呼叫的是angular.copy方法。listener在執行的時候,可能會修改資料從而觸發其他的listener或者自己直到沒有檢測到改變為止。Rerun Iteration的上限是10次,這樣能夠保證不會出現死迴圈的情況。
$watch的基本結構如下:
//$scope.$watch(<function/expression>, <handler>); $scope.$watch('foo', function(newVal, oldVal) { console.log(newVal, oldVal); }); //or $scope.$watch(function() { return $scope.foo; }, function(newVal, oldVal) { console.log(newVal, oldVal); });
- $digest()
該方法會觸發當前scope以及child scope中的所有watchers,因為watcher的listener可能會改變model,所以$digest方法會一直觸發watchers直到不再有listener被觸發。當然這也有可能會導致死迴圈,不過angular也幫我們設定了上限10!否則會丟擲“Maximum iteration limit exceeded.”。
通常,我們不在controller或者directive中直接呼叫$digest方法,而是調$apply方法,讓$apply方法去呼叫$digest方法。
如何呼叫該方法呢?
$scope.$digest();
- $apply(exp)
Param | Type | Details |
exp (optional) |
string function() |
An angular expression to be executed.
|
個人理解,$apply方法就是將$digest方法包裝了一層,exp是可選引數,可以是一個string,也可以是function(scope)。虛擬碼(來自官方文件)如下:
function $apply(expr) { try { return$eval(expr); } catch(e) { $exceptionHandler(e); } finally { $root.$digest(); } }
$apply方法使得我們可以在angular裡面執行angular框架之外的表示式,比如說:瀏覽器DOM事件、setTimeout、XHR或其他第三方的庫。由於我們要在angular框架內呼叫,我們必須得準備相應的scope。呼叫方式如下:
$scope.$apply('foo = "test"'); //or $scope.$apply(function(scope) { scope.foo = 'test'; }); //or $scope.$apply(function(){ $scope.foo = 'test'; });
- $watch、$digest、$apply是如何與檢視的更新相關聯的呢?
- directive給$scope上的一個model註冊$watch來監視它的變化,listener會去更新DOM元素的值。
- directive給DOM中的一些元素註冊event handler,它們會取得DOM中元素的值,然後更新到$scope上的model中去。它也會觸發$apply或者$digest。
- 當你通過框架更新了$scope上model的值,比如說:$http.get(),當它完成後也會觸發$digest方法。
- $digest會去檢查directive註冊的$watch,發現值被修改就會觸發相關聯的handler,然後更新DOM元素。
至於angular js為什麼要這麼做,請看我上一篇部落格angular js之scope.$apply方法。
- $watch
- 當$scope上的值發生變化時,儘量在directive中使用$watch去更新DOM。
- 儘量不要再controller中使用$watch方法,它會增加測試的複雜度,而且也不必要。可以使用scope上的方法去更新被改變的值。
- $digest、$apply
- 在directive中使用$digest/$apply使angular知道一個非同步請求完成後的變化,比如說DOM Event。
- 在service中使用$digest/$apply使angular知道一個非同步操作已經完成,比如說WebSocket、或者第三方的庫。
- 儘量不要再controller中使用$digest/$apply,這樣的話測試起來會比較困難。
===============================================================================
- 關於angular.equals方法
該方法支援value types,regular expressions、arrays、objects。官方文件寫的很清楚:
Two objects or values are considered equivalent if at least one of the following is true:
- Both objects or values pass === comparison.
- Both objects or values are of the same type and all of their properties are equal by comparing them with angular.equals.
- Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
- Both values represent the same regular expression (In JavasScript, /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual representation matches).
During a property comparison, properties of function type and properties with names that begin with $ are ignored.
Scope and DOM Window objects are being compared only by identify (===).