1、angularjs(1.x)數據雙向綁定原理
AngularJs 爲 scope 模型上設置了一個 監聽隊列,用來監聽數據變化並更新 view 。每次綁定一個東西到 view(html) 上時 AngularJs 就會往 watch,用來檢測它監視的 model 裏是否有變化的東西。當瀏覽器接收到可以被 angular context 處理的事件時,digest 會遍歷所有的 $watch。從而更新DOM。
scope下,我們創建一個監控器watch,watch,當我們每次綁定到UI上的時候就自動創建一個watchers。
注:即便我們在watch。
digest循環就會觸發。watch,如果watch都沒有變化。這樣就能夠保證每個model都已經不會再變化。這就是髒檢查(Dirty Checking)機制。
apply 我們可以直接理解爲刷新UI。如果當事件觸發時,你調用digest檢測機制就不會觸發。
總結:
- 只有在watch
- $apply決定事件是否可以進入angular context
- $digest 循環檢查model時最少兩次,最多10次(多於10次拋出異常,防止無限檢查)
- AngularJs自帶的指令已經實現了$apply,所以不需要我們額外的編寫
- 在自定義指令時,建議使用帶function參數的$apply
2、Angular2中的雙向綁定原理
雙向綁定,也是Angular2的核心概念之一,Angular2的雙向綁定是這樣的:
- data=>view:數據綁定,模板語法是 []
- view=>data:事件綁定,模板語法是 ()
- Angular其實並沒有一個雙向綁定的實現,他的雙向綁定就是數據綁定+事件綁定,模板語法是 [()] 。
Angular2官方給的例子:
<!--value是數據綁定,input是事件綁定-->
<input [value]="currentHero.name"
(input)="currentHero.name=$event.target.value"
>
<!--等價-->
<input [(ngModel)]="currentHero.name">
上面是input空間的雙向綁定語法,很清楚的說明了雙向綁定與兩個單向綁定的關係。這裏沒有使用ngModule
語法,ngModule
語法內部實現與這個差不多。
事件綁定
- 用戶操作出發DOM事件通知
- Angular監聽到了通知,然後執行模板語法,上面的例子就是將input控件的輸入值賦給了
currentHero.name
。
數據綁定
由於js語言並沒有屬性變化通知的機制,所以angular也不知道誰發生了變化,在什麼時候變了。Angular的變化機制是:
上面的例子中input的數據綁定過程如下:
- 代碼修改了
currentHero.name
的值。 - 觸發整個組件樹的變化檢查。
- input顯示了修改後的值。
數據何時變化
主要入下集中情況可能改變數據:
- 用戶輸入操作,比如點擊,提交等。
- 請求服務端數據。
- 定時事件,比如
setTimeout
,setInterval
。
這幾點有個共同點,就是他們都是異步的。也就是說,所有的異步操作是可能導致數據變化的根源因素。
如何通知變化
在Angularjs中是由代碼$scope.$apply()
或者$scope.$digest
觸發,而Angular2接入了ZoneJS
,由它監聽了Angular所有的異步事件。ZoneJS重寫了所有的異步API(所謂的猴子補丁,MonkeyPath)。ZoneJS會通知Angular可能有數據發生變化,需要檢測更新。
變化檢測原理 -- 髒檢查
所謂髒檢查就是存儲所有變量的值,每當可能有變量發生變化需要檢查時,就將所有變量的舊值跟新值進行比較,不相等就說明檢測到變化,需要更新對應的視圖。
AngularJS與Angular2變化檢測的區別
Angularjs的變化檢測機制也是髒檢查,而Angular2的變化檢測性能比Angularjs提升了很多。
Angular2
Angular的核心是組件化,組件的嵌套會使得最終形成一棵組件樹。Angular的變化檢測可以分組件進行,每個組件都有對應的變化檢測器ChangeDetector
。可想而知,這些變化檢測器也會構成一棵樹。
另外,Angular的數據流是自頂而下的,從父組件到子組件單向流動。單向數據流向保證了高效、可預測的變化檢測,儘管檢查了負組件之後,子組件可能會改變父組件的數據使得父組件需要再次被檢查,這是不被推薦的數據處理方式。在開發模式下,Angular會進行二次檢查,如果出現上述情況,二次檢查就會報錯:ExpressionChangedAfterItHasBeenCheckedError
。而在生產環境中,髒檢查只會執行一次。
Angularjs
相比之下,Angularjs採用的是雙向數據流,錯綜複雜的數據流使得他不得不多次檢查,使得數據最終趨向穩定。理論上,數據永遠不可能穩定,Angularjs的策略是,髒檢查超過10次就認定程序有問題。
變化檢測優化
優化策略
有2個思路:
- OnPush策略:我知道我沒變,別查我。
- 手動控制刷新:我變了,只查我。
變化檢測策略 OnPush
Angular還讓開發者擁有制定變化策略的能力。
export enum ChangeDetectionStrategy {
OnPush, // 表示變化檢測對象的狀態爲`CheckOnce`
Default, // 表示變化檢測對象的狀態爲`CheckAlways`
}
從ChangeDetectionStrategy
可以看到,Angular有兩種變化檢測策略。Default
是Angular默認的變化檢測策略,也就是髒檢查(只要有值發生變化,就全部檢查)。開發者可以根據場景來設置更加高效的變化檢測方式:OnPush
。OnPush
策略,就是隻有當輸入數據的引用發生變化或者有事件觸發時,組件進行變化檢測。
@Component({
template: `
<h2>{{vData.name}}</h2>
<span>{{vData.email}}</span>
`,
// 設置該組件的變化檢測策略爲onPush
changeDetection: ChangeDetectionStrategy.OnPush
})
class VCardCmp {
@Input() vData;
}
比如上面這個例子,當vData
的屬性值發生變化的時候,這個組件不會發生變化檢測,只有當vData
重新賦值的時候纔會。一般,只接受輸入的木偶子組件(dumb components)比較適合採用onPush
策略。
那什麼時候只要對象的屬性值發生變化,整個對象的引用就變了呢?不可變對象(Immutable Object)。當組件中的輸入對象是不變量時,可採用onPush
變化檢測策略,減少變化檢測的頻率。換個角度來說,爲了更加智能地執行變化檢測,可以在只接受輸入的子組件中採用onPush
策略。
手動控制變化檢測
Angular不僅可以讓開發者設置變化檢測策略,還可以讓開發者獲取變化檢測對象引用ChangeDetectorRef
,手動去操作變化檢測。變化檢測對象引用給開發者提供的方法有以下幾種:
-
markForCheck()
:將檢查組件的所有父組件所有子組件,即使設置了變化檢測策略爲onPush
。 -
detach()
:將變化檢測對象脫離檢測對象樹,不再進行變化檢查;結合detectChanges
可實現局部變化檢測。(採用onPush
策略之後的組件detach()
無效) -
detectChanges()
:將檢測該組件及其子組件,結合detach
可實現局部檢測。 -
checkNoChanges()
: 檢測該組件及其子組件,如果有變化存在則報錯,用於開發階段二次驗證變化已經完成。 -
reattach()
:將脫離的變化檢測對象重新鏈接到變化檢測樹上。
那麼,如果是Observable的話,它會訂閱所有的變量變化,只要在訂閱回調函數中手動觸發變化檢測即可實現最小成本的檢測(仍採用onPush
變化檢測策略)。舉個例子:
@Component({
template: '{{counter}}',
changeDetection: ChangeDetectionStrategy.OnPush
})
class CartBadgeCmp {
@Input() addItemStream:Observable<any>;
counter = 0;
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
this.addItemStream.subscribe(() => {
this.counter++; // 數據模型發生變化
this.cd.markForCheck(); // 手動觸發檢測
})
}
}
另外,當數據模型變化太過頻繁,我們可自定義變化檢測的時機。舉個例子:
@Component({
template: `{{counter}}
<input type="check" (click)="toggle()">`,
})
class CartBadgeCmp {
counter = 0;
detectEnabled = false;
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
// 每10毫秒增加1
setInterval(()=>{this.counter++}, 10);
}
toggle(){
if( this.detectEnabled ){
this.cd.reattach(); // 鏈接上變化檢測樹
}
else{
this.cd.detach(); // 脫離變化檢測樹
}
}
}
Angular2與Angularjs比較
Angular2與Angularjs都採用變化檢測機制,前者優於後者主要體現在:
- 單項數據流動
- 以組件爲單位維度獨立進行檢測
- 生產環境只進行一次檢查
- 可自定義的變化檢測策略:
Default
和onPush
- 可自定義的變化檢測操作:
markForcheck()
、detectChanges()
、detach()
、reattach()
、checkNoChanges()
- 代碼實現上的優化,據說採用了VM friendly的代碼。