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、在視圖中更改數據,為何會影響作用域。$apply偽代碼:
簡而言之,你在 $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:
&
當點擊時,ng會將fn1包裝到wrappering function中,然後傳入到$scope.$apply()。因此我的fn1也會被正常執行。
如果點擊後數據model數據發生變化,新一輪的$digest loop循環也會被觸發。
1、每個雙向綁定的元素都有一個watcher2、在某些事件發生的時候,調用digest臟數據檢測。這些事件有:表單元素內容變化、Ajax請求響應、點擊按鈕執行的函數等。3、臟數據檢測會檢測rootscope下所有被watcher的元素。
最近正好閱讀了一些這方面的書,自己做的一些筆記,希望對你有所幫助,請忽略格式和錯別字,仔細閱讀完 就能回答題主的問題了理解angularJS的內部運作機制
- 響應DOM事件更新模型
angularJS通過不同指令註冊DOM事件監聽器來將DOM樹的變化傳播給模型.事件監聽器中的代碼通過修改$scope暴漏的變數來更新模型.
我們可以模擬編寫一個ng-model指令效果相同的的簡單指令 用來闡述更新模型的技術要點 代碼如下angular.module("internals", [])
.directive("simpleModel", function ($parse) {
return function (scope,element,attrs) {
//$parse 服務 該服務即可用來根據作用域計算AngularJS表達式的值,又可以在某個作用域上設置值,
//當傳入一個表達式作為參數來調用$parse服務時,他會放回一個getter函數
//如果傳入的表達式可被賦值,那麼這個getter函數就會帶有一個assign屬性(屬性值是對應的setter函數)
var modelGetter = $parse(attrs.simpleModel);
var modelSetter = modelGetter.assign;
element.bind("input", function () {
var value =element.val();
modelSetter(scope,value);
});
};
});
- 將模型變化傳播給DOM
也可以使用$parse服務編寫一個簡單的ng-bind指令,該指令可以將模型值渲染為DOM節點中的文字,代碼如下
.directive("simpleBind", function ($parse) {
return function (scope,element,attrs) {
var modelGetter = $parse(attrs.simpleBind);
element.text(modelGetter(scope));
}
});
- 同步DOM和模型變化
上面的兩個指令編寫之後,就可以試著在HTML代碼中使用它們,我們期望這兩個新的指令能想ng-model和ng-bind那樣工作
&
&
&&
&
不幸的是,運行的代碼得不到我們期望的效果!初始渲染的效果是正確的,但input的變化並不會觸發span的同步更新
快速檢查simple-bind指令就會發現這個指令只做了初始化模型的渲染,並沒有監視模型的變化,也就不會對模型變化做出任何反應,可以在作用域實例使用$watch方法來修復這個問題
angular.module("internals", [])
.directive("simpleModel", function ($parse) {
return function (scope,element,attrs) {
//$parse 服務 該服務即可用來根據作用域計算AngularJS表達式的值,又可以在某個作用域上設置值,
//當傳入一個表達式作為參數來調用$parse服務時,他會放回一個getter函數
//如果傳入的表達式可被賦值,那麼這個getter函數就會帶有一個assign屬性(屬性值是對應的setter函數)
var modelGetter = $parse(attrs.simpleModel);
var modelSetter = modelGetter.assign;
//從Model更新DOM
scope.$watch(modelGetter, function (newVal,oldVal) {
element.val(newVal)
});
//從DOM更新Model
element.bind("input", function () {
var value =element.val();
modelSetter(scope,value);
});
};
})
.directive("simpleBind", function ($parse) {
return function (scope,element,attrs) {
var modelGetter = $parse(attrs.simpleBind);
//$watch方法可以讓我們監視模型的變化 每當有變化時就執行一個函數做出響應,$watch方法的特徵很明顯.
//scope.$watch(watchExpression,modelChangeCallback)
//watchExpression 既可以是一個函數,也可以是一個angularJs表達式 modelChangeCallback是一個回調函數.會在每次watchExpression的值法師變化時被調用,
//這個函數可以接受兩個值,分別是watchExpression變化前後的值
scope.$watch(modelGetter, function (newVal,oleVal) {
element.text(modelGetter(scope));
});
}
});
- scope.$apply --打開angularJS事件的鑰匙
- DOM事件(如:用戶修改了input的值,然後點擊一個按鈕,調用一個JS函數或者執行其他操作)
- XHR響應觸發回調
- 瀏覽器的地址變化
- 計時器觸發回調
的卻 如果上述的任何一種情況都沒發生,那麼監控模型的變化時沒有意義的,此時頁面上什麼事情都沒有發生,模型也沒有變化,重新DOM也就毫無必要
angularJS只會在被明確告知的情況下才會啟動它的模型模型監控機制,為了讓這種複雜的機制運行起來需要在scope對象上執行$apply方法回到之前的simple-model指令,可以在input值每次發生變化後執行$apply方法(這樣每次按鍵變化都會傳播給模型.這也是ng-model指令的默認行為)angular.module("internals", [])
.directive("simpleModel", function ($parse) {
return function (scope,element,attrs) {
//$parse 服務 該服務即可用來根據作用域計算AngularJS表達式的值,又可以在某個作用域上設置值,
//當傳入一個表達式作為參數來調用$parse服務時,他會放回一個getter函數
//如果傳入的表達式可被賦值,那麼這個getter函數就會帶有一個assign屬性(屬性值是對應的setter函數)
var modelGetter = $parse(attrs.simpleModel);
var modelSetter = modelGetter.assign;
//從Model更新DOM
scope.$watch(modelGetter, function (newVal,oldVal) {
element.val(newVal)
});
//從DOM更新Model
element.bind("input", function () {
scope.$apply(function () {
var value =element.val();
modelSetter(scope,value);
});
});
};
})
.directive("simpleBind", function ($parse) {
return function (scope,element,attrs) {
var modelGetter = $parse(attrs.simpleBind);
//$watch方法可以讓我們監視模型的變化 每當有變化時就執行一個函數做出響應,$watch方法的特徵很明顯.
//scope.$watch(watchExpression,modelChangeCallback)
//watchExpression 既可以是一個函數,也可以是一個angularJs表達式 modelChangeCallback是一個回調函數.會在每次watchExpression的值法師變化時被調用,
//這個函數可以接受兩個值,分別是watchExpression變化前後的值
scope.$watch(modelGetter, function (newVal,oleVal) {
element.text(modelGetter(scope));
});
}
});
本人簡單模擬了一下Angularjs的雙向數據綁定:簡單模擬了Angularjs的雙向數據綁定 - 獨孤求掰 - 知乎專欄
這有簡易版的實現方式:
angular雙向綁定簡單實現
angular把修改數據模型的入口都接管了。你不能用settimeout,必須用$timeout。不能用setinterval,必須用$interval。不能用xhr,必須用$http。等等
不自己寫指令的話 直接用ng-mode 就可以了 我也是初學者 多多指教
我猜是發布/訂閱模式吧
題主可以看看這邊文章,詳細介紹數據綁定的實現
JavaScript 實現簡單的雙向數據綁定
也可以通過JavaScript的setter 來實現推薦閱讀:
※AngularJS 1.x 的缺點如何解決?
※angular 自定義指令如何操作DOM的問題?
※新手,覺得Angularjs好難,該如何學習Angularjs?
※Handlebars 和angularjs有什麼區別?分別在什麼情況下使用?
※react.js,angular.js,vue.js學習哪個好?
TAG:前端開發 | JavaScript | MVC | AngularJS |