angularJS之$watch、$digest和$apply方法

pengisgood發表於2013-10-31

  最近專案上使用了比較多的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.
  • string: Evaluated as expression
  • function(scope): called with current scope as a parameter.
listener
(optional)

function()

string

Callback called whenever the return value of the watchExpressionchanges.
  • string: Evaluated as expression
  • function(newValue, oldValue, scope): called with current and previous values as parameters.
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.
  • string: execute using the rules as defined in expression.
  • function(scope): execute the function with current scope parameter.

    個人理解,$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是如何與檢視的更新相關聯的呢?
  1. directive給$scope上的一個model註冊$watch來監視它的變化,listener會去更新DOM元素的值。
  2. directive給DOM中的一些元素註冊event handler,它們會取得DOM中元素的值,然後更新到$scope上的model中去。它也會觸發$apply或者$digest。
  3. 當你通過框架更新了$scope上model的值,比如說:$http.get(),當它完成後也會觸發$digest方法。
  4. $digest會去檢查directive註冊的$watch,發現值被修改就會觸發相關聯的handler,然後更新DOM元素。

     至於angular js為什麼要這麼做,請看我上一篇部落格angular js之scope.$apply方法

  • $watch
  1. 當$scope上的值發生變化時,儘量在directive中使用$watch去更新DOM。
  2. 儘量不要再controller中使用$watch方法,它會增加測試的複雜度,而且也不必要。可以使用scope上的方法去更新被改變的值。
  • $digest、$apply
  1. 在directive中使用$digest/$apply使angular知道一個非同步請求完成後的變化,比如說DOM Event。
  2. 在service中使用$digest/$apply使angular知道一個非同步操作已經完成,比如說WebSocket、或者第三方的庫。
  3. 儘量不要再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:

  1. Both objects or values pass === comparison.
  2. Both objects or values are of the same type and all of their properties are equal by comparing them with angular.equals.
  3. Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
  4. 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 (===).

相關文章