Angular变化检测机制:改善的脏检查
本文鏈接:https://blog.csdn.net/fen747042796/article/details/75152336
前端展示的頁面是由視圖和數據共同構成的,視圖模板定義了頁面的框架,而數據定義了頁面具體的顯示內容。
而數據發生變化的時候,我們需要及時將變化的內容更新到視圖中,否則用戶看到的數據就是不正確的。系統及時感知到數據模型的變化,然后通過計算更新到視圖中,這是每個前端框架都需要解決的問題。這前一半部分就是所謂的變化檢測。
數據何時變化
接下來的問題是,數據何時變化,哪些因素會引起數據變化?在數據雙向綁定的分析和簡單實現中曾經分析過,主要有如下幾種情況可能也改變數據:
- 用戶輸入操作,比如點擊,提交等
- 請求服務端數據
- 定時事件,比如setTimeout,setInterval
這幾點有一個共同點,就是它們都是異步的。也就是說,所有的異步操作是可能導致數據變化的根源因素。
如何通知變化
那么,在Angular中是誰來通知數據即將變化的呢?在AngularJS中是由代碼$scope.$apply()或者$scope.$digest觸發,而Angular接入了ZoneJS,由它監聽了Angular所有的異步事件。ZoneJS是怎么做到的呢?其實它重寫了所有的異步api(所謂的猴子補丁Monkey patch)!ZoneJS會通知Angular可能有數據發生變化,需要檢測更新。
變化檢測原理
Angular得到需要重新檢查數據模型,更新視圖的通知后,是怎么執行變化檢測的呢?答案是,臟檢查。考慮到還沒聽說過臟檢查的同學,這里解釋一下,臟檢查其實就是存儲所有變量的值,每當可能有變量發生變化需要檢查時,就將所有變量的舊值跟新值進行比較,不相等就說明檢測到變化,需要更新對應視圖。
改善的臟檢查
接觸過AngularJS的同學肯定知道,它使用的變化檢測機制也是臟檢查。那么,同是臟檢查的背后,有何不同呢?為何Angular自稱變化檢測的性能比起AngularJS提升了很多?
Angular的核心是組件化,組件的嵌套會使得最終形成一棵組件樹。Angular的變化檢測可以分組件進行,每個組件都有對應的變化檢測器ChangeDetector。可想而知,這些變化檢測器也會構成一棵樹。
另外,Angular的數據流是自頂而下,從父組件到子組件單向流動。單向數據流向保證了高效、可預測的變化檢測。盡管檢查了父組件之后,子組件可能會改變父組件的數據使得父組件需要再次被檢查,這是不被推薦的數據處理方式。在開發模式下,Angular會進行二次檢查,如果出現上述情況,二次檢查就會報錯:ExpressionChangedAfterItHasBeenCheckedError(關于這個問題的答案,可以在參考資料中找到)。而在生產環境中,臟檢查只會執行一次。
相比之下,AngularJS采用的是雙向數據流,錯綜復雜的數據流使得它不得不多次檢查,使得數據最終趨向穩定。理論上,數據可能永遠不穩定。AngularJS給出的策略是,臟檢查超過10次,就認為程序有問題,不再進行檢查。這個10,我不知道它的給出依據是什么,也許是個經驗值吧。
變化檢測策略onPush
Angular還讓開發者擁有定制變化檢測策略的能力。
從ChangeDetectionStrategy可以看到,Angular有兩種變化檢測策略。Default是Angular默認的變化檢測策略,也就是上述提到的臟檢查(只要有值發生變化,就全部檢查)。開發者可以根據場景來設置更加高效的變化檢測方式:onPush。onPush策略,就是只有當輸入數據的引用發生變化或者有事件觸發時,組件才進行變化檢測。
比如上面這個例子,當vData的屬性值發生變化的時候,這個組件不會發生變化檢測,只有當vData重新賦值的時候才會。一般,只接受輸入的木偶子組件(dumb components)比較適合采用onPush策略。
那什么時候只要對象的屬性值發生變化,整個對象的引用就變了呢?不可變對象(Immutable Object)。當組件中的輸入對象是不變量時,可采用onPush變化檢測策略,減少變化檢測的頻率。換個角度來說,為了更加智能地執行變化檢測,可以在只接受輸入的子組件中采用onPush策略。
變化檢測對象引用
Angular不僅可以讓開發者設置變化檢測的策略,還可以讓開發者獲取變化檢測對象引用ChangeDetectorRef,手動去操作變化檢測。變化檢測對象引用給開發者提供的方法有以下幾種:
- markForCheck():將檢查組件的所有父組件所有子組件,即使設置了變化檢測策略為onPush
- detach():將變化檢測對象脫離檢測對象樹,不再進行變化檢查;結合detectChanges可實現局部變化檢測
- detectChanges():將檢測該組件及其子組件,結合detach可實現局部檢測
- checkNoChanges(): 檢測該組件及其子組件,如果有變化存在則報錯,用于開發階段二次驗證變化已經完成
- reattach():將脫離的變化檢測對象重新鏈接到變化檢測樹上
那么,如果是Observable的話,它會訂閱所有的變量變化,只要在訂閱回調函數中手動觸發變化檢測即可實現最小成本的檢測(仍采用onPush變化檢測策略)。舉個例子:
另外,當數據模型變化太過頻繁,我們可自定義變化檢測的時機。舉個例子:
一個注意點是,采用onPush策略之后的組件detach()無效,具體可參考這里。
疑惑點
在Angular源碼中看到變化檢測對象有如下幾種狀態:?
+?CheckOnce:表示只檢查一次,調用detectChanges之后狀態將會變為Checked?
+?Checked:表示在狀態變為CheckOnce之前會跳過所有檢測?
+?CheckAlways:表示總是接受變化檢測,每次調用detectChanges后狀態還是CheckAlways?
+?Detached:代表變化檢測對象脫離了變化檢測對象樹,不再進行變化檢測?
+?Errored:表述變化測試對象發生錯誤,變化檢測實效?
+?Destroyed:表示變化檢測對象已經被銷毀
而OnPush策略表示變化檢測對象的狀態為CheckOnce。那么設置OnPush策略的組件為什么是引用發生變化之后才會執行變化檢測的?檢測之后狀態從CheckOnce變成Checked,然后是如何變成CheckOnce的?
P.S. 嘗試閱讀Angular變化檢測這部分的源代碼,實在不知從何下手,網上資料甚少,求閱讀源碼經驗分享。
總結
Angular與AngularJS都采用臟檢查的變化檢測機制,前者優于后者主要體現在:
- 單向數據流向
- 以組件為單位維度獨立進行
- 生產環境只進行一次檢查
- 可自定義的變化檢測策略:?Default和onPush
- 可自定義的變化檢測操作:markForcheck(),detectChanges(),detach(),?reattach(),checkNoChanges()
- 代碼實現上的優化,據說采用了VM friendly的代碼(這點我也不太明白,就隨便提一下)
參考資料:
- Tero Parviainen: Change And Its Detection In JavaScript Frameworks
- Victor Savkin: Change Detection in Angular
- Pascal Precht: Angular Change Dection Explained
- Maxim Koretskyi:Everything you need to know about change detection in Angular
- Maxim Koretskyi:Angular’s $digest is reborn in the newer version of Angular
- Wojciech Kwiatek:Understanding Angular 2 change detection?
- Ben Nadel: Change Detection Strategy Appears To Override The ChangeDetectorRef In Angular 2 RC 3
- Juri: Tuning Angular’s Change Detection
- Maxim Koretskyi:Everything you need to know about the?ExpressionChangedAfterItHasBeenCheckedError?error
總結
以上是生活随笔為你收集整理的Angular变化检测机制:改善的脏检查的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 懂球帝怎么买彩票
- 下一篇: 韩国运营商 SK 电讯等四家公司牵头成立