Angular提供了數據綁定的功能。所謂的數據綁定,就是把組件類中的數據與頁面的DOM元素關聯起來,當數據發生變化時,Angular能夠監測到這些變化,並對其綁定的DOM元素進行相應的更新(如何更新這是在模板中的各種情況來決定的,而且更新也只是更新變化的局部,所以重點是Angular如何准確的定位到變化的地方)。
通常,異步事件的發生會導致組件中的數據發生變化,但Angular並不是去捕獲對象的變動,而是在適當的時機去檢測對象的值是否被改變。這個時機是由NgZone這個服務去掌控的,它會獲取整個應用的執行上下文,能夠對相關的異步事件的發生、完成、異常等進行捕獲,然后驅動Angular的變化檢測機制去執行。
我們來整理一下:
- 首先,數據發生變化
- 被NgZone服務捕獲
- NgZone服務通知Angular有數據發生改變
- Angular接收到有數據發生了變化的信號,對組件執行變更監測處理
下面我們來大概說明一下:
一、什么觸發了數據變化
一般有3中情況觸發數據改變:
- 用戶動作,比如mousemove,input,click
- ajax,從server獲取數據
- 定時操作,比如setTimeout,setInterval
共同特點是都是異步操作。
二、NgZone服務捕獲異步事件並通知Angular執行對象比較
Angular需要一種能夠捕獲異步操作的機制,Angular本身沒有這種功能,所以它引入了Zone庫。
NgZone服務就是使用Zone實現的,NgZone從Zone中fork了一份實例 ,是Zone派生出來的一個子Zone,這個子Zone擁有Angular執行環境的執行上下文,所以在Angular環境內注冊的異步操作都運行在這個子Zone上。
Zone對外提供了一些鈎子,能夠在異步操作發生、完成、錯誤時調用這些鈎子達到通知外部的功能。
Angular中,就是在NgZone服務中監聽了Zone提供的鈎子,在異步操作完成后去執行變化監測。
三、Angular如何執行對象比較
Angular應用由組件組成,形成一棵組件樹。每一個組件都有自己的變化監測器,所以也組成了一棵變化監測樹。
每次變更監測都是從上到下執行的,先從根組件開始,從上往下地監測每一個組件的變化。
組件樹中的每一個組件都有自己的變更監測器。這使得每個組件的變化監測相互獨立,可以靈活的控制變更監測的執行或者暫停。
下面來看一下變更監測器是如何工作的:
對於每一個組件,Angualr都會創建一個變更監測類的實例,在這個變更監測類的實例中,准確的記錄了當前組件的數據模型,當下次變更監測到來時,會被組件當前的數據模型和准確保存下來的數據模型進行比較,(怎么進行比較又是一個大問題)。
默認情況下,任何一個組件模型中的數據變化都會導致整個組件樹進行變化監測,但其實很多組件的輸入屬性沒有變化時,數據就不會發生改變,如此沒有必要對這樣的組件進行變化監測的操作。
那么如何讓Angular僅僅監測變化的組件呢?
前面提到過,Angular為每一個組件都創建了一個變化監測類的實例,該實例提供了一些方法可以手動管理變化監測。
當發生變化時,Angular會從根組件到子組件來監測每個組件是否發生了變化。Angular本身並不知道是哪個組件發生了變化,所以才遍歷所有組件進行監測。
但是開發者是知道哪個組件發生了變化,所以可以通過變化監測類的實例提供的方法給這個組件做標記,來通知Angular僅僅監測這個組件所在的路徑上的組件就可以了。
1 export declare abstract class ChangeDetectorRef { 2 /** 3 * 當視圖使用 OnPush(checkOnce)變更檢測策略時,把該視圖顯式標記為已更改,以便它再次進行檢查。 4 * 當輸入已更改或視圖中發生了事件時,組件通常會標記為臟的(需要重新渲染)。調用此方法會確保即使那些觸發器沒有被觸發,也仍然檢查該組件。 5 * 把根組件到該組件之間的這條路徑標記出來,通知Angular在下次觸發變化監測時必須檢測這條路徑上的組件。 6 */ 7 abstract markForCheck(): void; 8 /** 9 * 從變更檢測樹中分離開視圖。 已分離的視圖在重新附加上去之前不會被檢查。 與 detectChanges() 結合使用,可以實現局部變更檢測。 10 * 從變更監測樹上分離出當前的變更監測器,該組件的變化監測器將不會主動的執行變更監測,除非調用redetach方法重新安裝上 11 * 即使已分離的視圖已標記為臟的,它們在重新附加上去之前也不會被檢查。 12 */ 13 abstract detach(): void; 14 /** 15 * 檢查該視圖及其子視圖。與 detach 結合使用可以實現局部變更檢測。 16 */ 17 abstract detectChanges(): void; 18 /** 19 * 檢查變更檢測器及其子檢測器,如果檢測到任何更改,則拋出異常。 20 * 在開發模式下可用來驗證正在運行的變更檢測器是否引入了其它變更。 21 */ 22 abstract checkNoChanges(): void; 23 /** 24 * 把以前分離開的視圖重新附加到變更檢測樹上。 視圖會被默認附加到這棵樹上。 25 * 重新把變更監測器放在變更監測樹上 26 */ 27 abstract reattach(): void; 28 }
同時,也可以設置變更檢測策略,
當組件實例化之后,Angular 就會創建一個變更檢測器,它負責傳播組件各個綁定值的變化。 該策略是下列值之一:
-
ChangeDetectionStrategy#OnPush
把策略設置為CheckOnce
(按需)。 -
ChangeDetectionStrategy#Default
把策略設置為CheckAlways
。
2個優化的場景:
為組件設置了 OnPush
變更檢測策略(CheckOnce
而不是默認的 CheckAlways
),然后每隔一段時間強制進行第二輪檢測。
1 @Component({ 2 selector: 'my-app', 3 template: `Number of ticks: {{numberOfTicks}}`, 4 changeDetection: ChangeDetectionStrategy.OnPush, 5 }) 6 7 class AppComponent { 8 numberOfTicks = 0; 9 10 constructor(private ref: ChangeDetectorRef) { 11 setInterval(() => { 12 this.numberOfTicks++; 13 // require view to be updated 14 this.ref.markForCheck(); 15 }, 1000); 16 } 17 }
分離開變更檢測器以限制變更檢測的發生頻度
定義了一個帶有只讀數據的大型列表,這些數據預計每秒會變化很多次。 為了提高性能,我們檢測和更新列表的頻率就應該比實際發生的變化少得多。 要解決這個問題,就要分離開變更檢測器,並每隔五秒鍾顯式執行一次變更檢查。
1 constructor(private ref: ChangeDetectorRef, private dataProvider: DataListProvider) { 2 ref.detach(); 3 setInterval(() => { this.ref.detectChanges(); }, 5000); 4 }
准備面試使用,比較粗糙,各位看官見諒。