簡單來講變化檢測就是
Angular
用來檢測視圖與模型之間綁定的值是否發生了改變,當檢測到模型中綁定的值發生改變時,則同步到視圖上,反之,當檢測到視圖上綁定的值發生改變時,則回調對應的綁定函數。
總結起來, 主要有以下幾種狀況:html
setTimeout
,setInterval
Angular並非捕捉對象的變更,它採用的是在適當的時機去檢驗對象的值是否被改動,這個時機就是這些異步事件的發生。
這個時機是由 Zone.js
去掌控的,它獲取到了整個應用的執行上下文,可以對相關的異步事件發生、完成或者異常等進行捕獲,而後驅動 Angular 的變化監測機制執行。typescript
實際上Zone,js
有一個叫猴子補丁的東西。在Zone.js
運行時,就會爲這些異步事件作一層代理包裹,也就是說Zone.js運行後,調用setTimeout、addEventListener
等瀏覽器異步事件時,再也不是調用原生的方法,而是被猴子補丁包裝事後的代理方法。代理裏setup了鉤子函數, 經過這些鉤子函數, 能夠方便的進入異步任務執行的上下文.segmentfault
//如下是Zone.js啓動時執行邏輯的抽象代碼片斷 function zoneAwareAddEventListener() {...} function zoneAwareRemoveEventListener() {...} function zoneAwarePromise() {...} function patchTimeout() {...} window.prototype.addEventListener=zoneAwareAddEventListener; window.prototype.removeEventListener=zoneAwareRemoveEventListener; window.prototype.promise = zoneAwarePromise; window.prototype.setTimeout = patchTimeout;
關於Zone.js
的詳細內容能夠看這篇文章。promise
Angular都是組件化的, 而且全部的組件會構成一個組件樹, Angular
的變化檢測就是以組件進行的,,對於每個組件,都有一個對應的changeDetector
, 所以, changeDetector
之間也是一個樹狀結構, 最後咱們須要記住的一點是,每次變化監測都是從 Component 樹根開始的。瀏覽器
另外,Angular的數據流是自頂而下,從父組件到子組件單向流動。單向數據流向保證了高效、可預測的變化檢測。儘管檢查了父組件以後,子組件可能會改變父組件的數據使得父組件須要再次被檢查,這是不被推薦的數據處理方式。在開發模式下,Angular會進行二次檢查,若是出現上述狀況,二次檢查就會報錯:Expression Changed After It Has Been Checked Error
。而在生產環境中,髒檢查只會執行一次。
關於Expression Changed After It Has Been Checked Error
, 筆者曾經遇到過, 能夠看看這個例子app
經過未
咱們能夠主動控制Angular
的變化檢測異步
它有如下方法:函數
markForCheck()
:把根組件到該組件之間的這條路徑標記起來,通知Angular在下次觸發變化監測時必須檢查這條路徑上的組件。該方法可與下文的OnPush
模式搭配使用,能夠看這個例子。detach()
:從變化監測樹中分離變化監測器,該組件的變化監測器將再也不執行變化監測,除非再次手動執行reattach()方法。reattach()
:把分離的變化監測器從新安裝上,使得該組件及其子組件都能執行變化監測。detectChanges()
:手動觸發執行該組件到各個子組件的一次變化監測。下面以使用detectChanges()
組件化
// 在組件中注入 constructor(private changeDetectorRef: ChangeDetectorRef) { } // 直接使用 test() { this.changeDetectorRef.detectChanges() }
angular 提供了兩種變動檢測策略,除了上述得Default外還有一種OnPush
的檢測機制this
OnPush 與 Default 之間的差異: 當檢測到與子組件輸入綁定的值沒有發生改變時,變化檢測就不會深刻到子組件中去。
app.comonent.ts
@Component({ selector: 'app-root', template: ` <h1>{{title}}</h1> <h2>user.name: {{user.name}}</h2> <button type="button" (click)="changeUserName()"> 改變屬性 </button> <button type="button" (click)="changeUserObject()"> 改變對象 </button> <app-test [user]="user"></app-test> `, }) export class AppComponent { title = 'OnPush Demo'; user: User = new User({name: 'yunzhi'}); changeUserName() { this.user.name = 'new name'; } changeUserObject() { this.user = new User({name: 'new user'}); } }
test.component.ts
@Component({ selector: 'app-test', template: ` <div> <h3>test 組件</h3> <p> <label>User:</label> <span>{{user.name}}</span> </p> </div>`, // 使用OnPush模式只須要加上下面這段代碼 changeDetection: ChangeDetectionStrategy.OnPush }) export class TestComponent implements OnInit { @Input() user: User; constructor() { } ngOnInit() { } }
這時當咱們點擊改變屬性按鈕時test組件顯示的並不會變化,只有改變user得引用test組件顯示的纔會變化,以下圖所示
Angular
經過優秀的變化檢測機制,讓咱們可以在數據發生了改變時準確的最小範圍的修改DOM,同時,Angular
還提供了兩種檢測策略,和在一個組件中控制變化檢測的方法,靈活運用這些方法可讓咱們的應用更加高效,雖然如今的項目尚未到須要這種效率。