1. 程式人生 > >angularjs雙向資料繫結原理解析

angularjs雙向資料繫結原理解析

angularjs的雙向資料繫結

髒值(發生了變化的值)檢查不等於定時輪詢,而是特定事件觸發才會執行
只有指定事件觸發後才會進入髒值輪詢。
- DOM事件,譬如使用者輸入文字,點選按鈕等。(ng-click)
- XHR(ajax)響應事件 (http)Location(location)
- Timer事件(timeout,interval)
- 執行digest()apply()

$digest函式的作用是呼叫這個監控函式,並且比較它返回的值和上一次返回值的差異。如果不相同,監聽器就是髒的,它的監聽函式就應當被呼叫。

$digest$apply

  • 在Angular中,有a

    pplydigest兩個函式.

  • applyscope(或者是 direcvie 裡的 link 函式中的 scope)的一個函式,呼叫它會強制一次 digestapply 的標誌)。

差異

$apply可以帶引數,它可以接受一個函式,然後在應用資料之後,呼叫這個函式。所以,一般在整合非Angular框架的程式碼時,可以把程式碼寫在這個裡面呼叫。

當呼叫 digest調apply 的時候,會觸發作用域樹上的所有監控

原理

雙向資料繫結意味著當 view 中有任何資料發生變化會自動地反饋到 scope 的資料上,當 scope 模型發生變化時,view 中的資料也會更新到最新的值。

AngularJS 確實在幕後為 scope 模型上設定了一個 監聽佇列,用來監聽資料變化並更新 view 。

每次繫結一個東西到 view 上時 AngularJS 就會往 watchwatch,用來檢測它監視的 model 裡是否有變化的東西。

以一次click操作執行的監聽為例:
- 按下按鈕
- 瀏覽器接收到一個事件,進入 angular context
- digestwatch 是否變化
- 由於監視 scope.valwatch 報告了變化,它會強制再執行一次 digestdigest 迴圈沒有檢測到變化。
- 瀏覽器拿回控制權,更新與 $scope.val 新值相應部分的 DOM 。

digestmodeldigest 迴圈的次數達到了一定閾值(丟擲異常防止無限迴圈)。

程式碼示例(重點)

  • 使用了 JavaScript 中的 setTimeout() 來更新一個 scope model

  • 用指令設定一個 DOM 事件 listener 並且在該 listener 中修改了一些 models

頁面中繫結的message值不會重新整理。

$scope.setMsg = function() {  
    setTimeout(function() {  
        $scope.message = 'hello world';  
        console.log('message:' + $scope.message);  
    }, 2000);  
}  
$scope.setMsg();

要想解決以上問題[message值不實時重新整理]有以下三種方法。

1、 直接使用:$scope.$apply();

setTimeout(function() {  
    $scope.message = 'hello world';  
    console.log('message:' + $scope.message2);
    $scope.$apply();
}, 2000);

2、 $scope.$apply(執行相應操作)

$scope.$apply可以替換為$scope.$digest

setTimeout(function() {  
    $scope.$apply(function() {  
        $scope.message1 = 'hello world';   
        console.log('message1:' + $scope.message);  
    });  
}, 2000);

3、 使用angular封裝的$timeout

$scope.$apply可以替換為$scope.$digest
- $timeout是angular對於setTimeout的封裝,$interval是angular對於setInterval的封裝。
- 會自動在內部函式執行完後呼叫$scope.$apply();(髒值檢查)。

$scope.setMsg = function() {  
    $timeout(function() {  
        $scope.message2 = 'hello world';  
        console.log('message2:' + $scope.message2);
    }, 2000);  
} 

注:

  1. 在取message的值時必須在controller作用域內

  2. $timeout需要在控制器中注入後使用

使用$watch監聽的潛在坑點

先大致介紹下$watch.
- $scope.$watch()可用於監聽變數、陣列或物件。有三個引數
- 第一個引數:被監聽的物件[必填]
- 第二個引數:監聽到變化時執行的函式[必填]
- 第三個引數:布林值,true意為監聽監聽物件內容的改變,false意為監聽監聽物件地址或引用的改變[選填,預設false]
- 要想取消監聽只需執行watch函式的返回值(沒有理解就看看下邊的程式碼)

var cancelWatch = $scope.$watch('message2', function(newValue, oldValue) {
//監聽到變化執行的操作
},true);
//取消監聽
cancelWatch();

之前一直以為可以用$watch監聽到變化以後強制執行$scope.$apply();,直到自己寫程式碼測試才發現這麼寫會報錯。

$scope.setMsg = function() {  
    setTimeout(function() {  
        $scope.message2 = 'hello steven';  
    }, 2000);  
}

var cancelWatch = $scope.$watch('message2', function(newValue, oldValue) {
    $scope.$digest();
},true);

報錯資訊原描述:At any point in time there can be only one $digest or $apply operation in progress. This is to prevent very hard to detect bugs from entering your application. The stack trace of this error allows you to trace the origin of the currently executing $apply or$digest call, which caused the error.

也就是說在任意時刻只能有一個$digest$apply執行操作進行當前當前頁面的髒值檢查。

  • 初始化程式時監聽是開啟的
  • 初始化時其實已經通過變數提升改變了$scope.message2(此時相當於定義為了undefined)
  • $watch監聽到資料變化提前執行了$scope.$digest(),重複呼叫監聽報錯

取消 $timeout

var customTimeout = $timeout(function () {  
  // your code
}, 1000);

$timeout.cancel(customTimeout);

通過編譯執行階段流程理解資料雙向繫結

編譯階段:

  • ng-model 和 input 指令 在 標籤中設定了一個 keydown 監聽器
  • 在{{greeting}} 插值(也就是表示式)這裡設定了一個 $watch 來監測 username 的變化

執行階段:

  • 在 輸入框中按下 ‘X’ 鍵引起瀏覽器發出一個 keydown 事件
  • input 指令捕捉到輸入值的改變呼叫 $apply(“username = ‘X’;”) 進入Angular的執行環境來更新應用的資料模型
  • Angular將 username = ‘X’; 作用在資料模型之上,這樣 scope.username 就被賦值為 ‘X’ 了
  • $digest 輪循開始
  • $watch 列表中監測到 username 有一個變化,然後通知 {{greeting}} 插值表示式,進而更新DOM
  • 執行離開Angular的上下文,進而 keydown 事件結束,然後執行也就退出了 JavaScript的上下文;這樣 $digest 完成
  • 瀏覽器用更新了的值重新渲染檢視

END