AngularJS的數據雙向綁定是怎麼實現的?

如果只是簡單input的內容改變只要監聽事件就行,這一邊比較好理解,

但如果我通過$scope.attr="xxx"去改變屬性值,其內部是怎麼知道我修改了這個屬性並更新視圖的呢?


看到某個答案被頂了幾票, 擔心會誤導新手。

angular並不存在定時臟檢測。

angular對常用的dom事件,xhr事件等做了封裝, 在裡面觸發進入angular的digest流程。

在digest流程裡面, 會從rootscope開始遍歷, 檢查所有的watcher。

angular性能優化心得

談起angular的臟檢查機制(dirty-checking), 常見的誤解就是認為: ng是定時輪詢去檢查model是否變更。

其實,ng只有在指定事件觸發後,才進入$digest cycle:

  • DOM事件,譬如用戶輸入文本,點擊按鈕等。(ng-click)
  • XHR響應事件 ($http)
  • 瀏覽器Location變更事件 ($location)
  • Timer事件($timeout, $interval)
  • 執行$digest()或$apply()


那個定時的答案不正確,會誤導人,我之前寫過一篇,裡面有個很簡單的例子,更接近真實的做法:

Angular沉思錄(一)數據綁定 · Issue #10 · xufei/blog · GitHub

如果要深究其變更原理,可以看我翻譯的這篇:

Make-Your-Own-AngularJS/01.md at master · xufei/Make-Your-Own-AngularJS · GitHub


在另一個問題中,從相關問題跳到這裡。

之前有項目打算用angularJS,但由於工程師(程序猿)的"政治立場",最終沒有使用,但是對angularJS有一些不太深入的了解,若有不足之處煩請指正。

1、更改作用域為何會刷新視圖?

偽代碼:

2、在事件中更改作用域,為何會刷新視圖?

偽代碼:

3、在視圖中更改數據,為何會影響作用域。

偽代碼:

其中,有三個概念:

$watch:添加監聽

$digest():臟值檢查循環

$apply:提供上下文執行表達式

$apply偽代碼:

===============================================

以上為個人的一些見解,沒有在實際項目中使用angularJS的經驗,難免有不實之處,煩請指正。


簡而言之,你在 $scope.attr="xxx" 之後還要寫 $scope.$digest(); 或者 $scope.$apply(); 而這兩個方法就是檢查數據變化並更新界面的,是你主動調用的。


AngularJS 仔細看看官方的介紹文檔啊


最近我剛給我的www.gaclib.net 做了單向綁定。雙向綁定跟單向綁定是一樣的,只要你能從一個單向的表達式計算出他的逆表達式,也就是函數求逆函數那樣的,這樣一個雙向綁定就等同於兩個單向綁定了。

總而言之,就是把一個表達式裡面所有帶event的property的依賴關係找出來,然後利用cps變換把那些被監視的值的操作編譯成callback那樣,就完成了。我自己的GUI的data binding的表達式支持所有類型的表達式,這也意味著很多能用來作綁定的表達式是不存在逆表達式的。這也是我為什麼現在沒有直接支持雙向綁定得原因。不過也正因為如此,很多複雜的綁定在我這裡都能輕鬆做出來:GacLib - Getting Started

這裡面比較操蛋的一個問題是,對於a.b.c這種東西,如果a.bChanged和a.b.cChanged都存在的話,b的變化意味著之前綁定到cChanged上面的函數得解綁然後重新綁定到新的b上面。

這是我做的實現(雖然不是javascript)Gac Library -- C++ Utilities for GPU Accelerated GUI and Script 把一個表達式編譯成一堆callback。


有很多方式實現雙向綁定,目前AngularJS選擇了dirty check。簡單來說就是給每個需要綁定的元素加個watcher,緩存下oldValue,然後定時遍歷所有的watcher,比較newValue和oldValue,如果變化了做更新操作。


個人理解:有不對的請指正,謝謝。

當寫下表達式{{model}}時,ng在幕後偷偷為你做一件事:

在scope模型上設置一個watcher,用來監聽數據發生變化的時候更新view。ng會周期性(不指定時器)的運行一個函數來檢查scope模型數據是否發生變化。

watcher原理:

$scope.$watch("model", function(newValue, oldValue) {
//update the DOM with newValue
});

這時候,$digest就出來發力了。

在$digest循環中,watchers會被觸發,然後ng會檢測scope模型,如果檢測到發生變化,關聯到watcher的回調函數就會被調用。

下面問題來了,$digest循環什麼時候以什麼方式開始呢?

在調用了$scope.$digest()後,$digest循環就開始了。

你在一個ng-click指令對應的handler函數中更改了scope中的一條數據,此時AngularJS會自動地通過調用$digest()來觸發一輪$digest循環。當$digest循環開始後,它會觸發每個watcher。這些watchers會檢查scope中的當前model值是否和上一次計算得到的model值不同。如果不同,那麼對應的回調函數會被執行。調用該函數的結果,就是view中的表達式內容(譯註:諸如{{ model }})會被更新。

還有個小注意事項:》》》》》》ng並不會直接調用$digest()。而是調用$scope.$apply(),然後調用$rootScope.$digest()。So,一輪$digest循環在$rootScope開始,隨後會訪問到所有的children scope中的watchers。

$digest循環會運行多少次?

當一個$digest循環運行時,watchers會被執行來檢查scope中的models是否發生了變化。如果發生了變化,那麼相應的listener函數就會被執行。這涉及到一個重要的問題。如果listener函數本身會修改一個scope model呢?AngularJS會怎麼處理這種情況?

答案是$digest循環不會只運行一次。在當前的一次循環結束後,它會再執行一次循環用來檢查是否有models發生了變化。這就是臟檢查(Dirty Checking),它用來處理在listener函數被執行時可能引起的model變化。因此,$digest循環會持續運行直到model不再發生變化,或者$digest循環的次數達到了10次。因此,儘可能地不要在listener函數中修改model

Note: $digest循環最少也會運行兩次,即使在listener函數中並沒有改變任何model。正如上面討論的那樣,它會多運行一次來確保models沒有變化。

再舉列子:

假設button上有個方法fn1:

&

AngularJS 1.x 的缺點如何解決?
angular 自定義指令如何操作DOM的問題?
新手,覺得Angularjs好難,該如何學習Angularjs?
Handlebars 和angularjs有什麼區別?分別在什麼情況下使用?
react.js,angular.js,vue.js學習哪個好?

TAG:前端開發 | JavaScript | MVC | AngularJS |