digest循環和$apply

讓我們來看看Angular在後臺是如何工作的。如何只使用幾行代碼就得到神奇的數據綁定?最重要的是理解$digest循環是如何工作的,以及如何使用$apply()方法。

在標準的瀏覽器流程中,當事件被觸發時(比如點擊一個鏈接)。瀏覽器會執行註冊給該事件的回調函數。

頁面加載、$http請求返回響應、鼠標移動以及按鈕被點擊等情況都會觸發事件。

當事件被觸發時,JavaScript就會創建一個事件對象,並執行這個事件對象所在的監聽特定事件的所有函數。然後它會運行JavaScript函數內的回調方法,這會回到瀏覽器中,還可能更新DOM。

注意:同一時間不能運行兩個事件。瀏覽器會等待前一個事件處理程序執行完成,再調用下一個事件處理程序。

 

在非Angular JavaScript環境中,可以給div的點擊事件附加一個回調函數。無論何時,只要發現元素上的點擊事件,這個回調函數就會運行;

var div = document.getElementById("clickDiv");

div.addEventListener("click",function(evt){

      console.log("evt",evt);

})

1.1 $watch 列表

<!DOCTYPE html>
<html lang="zh_CN" data-ng-app="app">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
    <!-- Angular -->
    <script src="vendor/angular/angular.js"></script>

</head>

<body >
    <input type="text" class="form-control" id="orgName" ng-model="orgName">
 
 
     <h1>Hello {{name}}</h1>
</body>
</html>

 

無路何時,只要用戶更新這個輸入字段,UI中的{{name}}就會改變。發生這一變化是因爲我們把UI中的輸入字段綁定給了scope.name屬性。爲了更新這個視圖,Angular需要追蹤變化。它是通過給$watch列表添加一個監控函數做到這一點的。

$scope對象上的屬性只會在其被用於視圖時綁定。在上述例子中,我們只給$watch列表添加了一個函數。

記住,對於所有綁定給同一$scope對象的UI元素,只會添加一個$watch到$watch列表中。

這個$watch列表會在$digest循環中通過一個叫做“髒值檢查”的程序解析。
 

其次就是什麼時候去髒檢查

angular在我們所寫的絕大部分代碼中都會觸發比較事件。比如:controller 初始化的時候,ng-click事件和所有以ng-開頭的事件執行後,$http 回調完成後,都會觸發髒檢查。

當然,觸發髒檢查的點是在函數執行完之後,但不表明異步調用也執行完成,所以,如果我們的功能是異步的,那你會發現我們的改變並沒有更新到dom上。   

來個demo?好,請看:

function Ctrl($scope) {
    $scope.message = "Waiting 2000ms for update";                                                                                    
    setTimeout(function () {
        $scope.message = "Timeout called!";
        // AngularJS unaware of update to $scope
    }, 2000);
}

dom上顯示的message是 “Waiting 2000ms for update,永遠都不是 “Timeout called!”

這就是 $apply的應用場景,使用$scope.$apply()手動觸發髒檢查。其實angular還貼心的提供了一個$timeout,那 $timeout和setTimeout在功能上有什麼區別嗎?可以說沒有,唯一的區別就是,使用$timeout異步完成之後,angular會自動觸發$apply()

如果已經在一個具有$apply環境中,調用$apply()會拋出異常, 有空你可以試一試。

$apply()接受一個function的參數,function中被綁定的對象會被髒檢查,function不能是異步的哦,這個很好理解,如果讓你去實現這個apply函數,$apply()不帶參數時,會將當前作用域的所有監聽對象都髒檢查一遍,所以不帶參數是有害的, 必然會做很多無用的髒檢查,浪費性能。

再次就是如何髒檢查

髒檢查只要是由 $digest() 完成,$apply()被調用之後,最終會觸發 $digest() ,

$digest 主要代碼如下:

if ((watchers = current.$$watchers)) {
// process our watches
length = watchers.length;
while (length--) {
   try {
      watch = watchers[length];
      // Most common watches are on primitives, in which case we can short
      // circuit it with === operator, only when === fails do we use .equals
      if (watch && (value = watch.get(current)) !==(last = watch.last) &&  !(watch.eq ? equals(value, last) : (typeof value ==number' && typeof last == 'number'
                         && isNaN(value) && isNaN(last)))) {

                dirty = true;
                watch.last = watch.eq ? copy(value) : value;
                watch.fn(value, ((last === initWatchVal) ? value : last), current);
                if (ttl < 5) {
                  logIdx = 4 - ttl;
                  if (!watchLog[logIdx]) watchLog[logIdx] = [];
                  logMsg = (isFunction(watch.exp))
                      ? 'fn: ' + (watch.exp.name || watch.exp.toString())
                      : watch.exp;
                  logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
                  watchLog[logIdx].push(logMsg);
                }
              }


            } catch (e) {
              $exceptionHandler(e);
            }
          }

遍歷watchers,比較監視的屬性是否變化.

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章