Angular变化检测机制:改善的脏检查
本文鏈接:https://blog.csdn.net/fen747042796/article/details/75152336
前端展示的頁(yè)面是由視圖和數(shù)據(jù)共同構(gòu)成的,視圖模板定義了頁(yè)面的框架,而數(shù)據(jù)定義了頁(yè)面具體的顯示內(nèi)容。
而數(shù)據(jù)發(fā)生變化的時(shí)候,我們需要及時(shí)將變化的內(nèi)容更新到視圖中,否則用戶看到的數(shù)據(jù)就是不正確的。系統(tǒng)及時(shí)感知到數(shù)據(jù)模型的變化,然后通過(guò)計(jì)算更新到視圖中,這是每個(gè)前端框架都需要解決的問(wèn)題。這前一半部分就是所謂的變化檢測(cè)。
數(shù)據(jù)何時(shí)變化
接下來(lái)的問(wèn)題是,數(shù)據(jù)何時(shí)變化,哪些因素會(huì)引起數(shù)據(jù)變化?在數(shù)據(jù)雙向綁定的分析和簡(jiǎn)單實(shí)現(xiàn)中曾經(jīng)分析過(guò),主要有如下幾種情況可能也改變數(shù)據(jù):
- 用戶輸入操作,比如點(diǎn)擊,提交等
- 請(qǐng)求服務(wù)端數(shù)據(jù)
- 定時(shí)事件,比如setTimeout,setInterval
這幾點(diǎn)有一個(gè)共同點(diǎn),就是它們都是異步的。也就是說(shuō),所有的異步操作是可能導(dǎo)致數(shù)據(jù)變化的根源因素。
如何通知變化
那么,在Angular中是誰(shuí)來(lái)通知數(shù)據(jù)即將變化的呢?在AngularJS中是由代碼$scope.$apply()或者$scope.$digest觸發(fā),而Angular接入了ZoneJS,由它監(jiān)聽了Angular所有的異步事件。ZoneJS是怎么做到的呢?其實(shí)它重寫了所有的異步api(所謂的猴子補(bǔ)丁Monkey patch)!ZoneJS會(huì)通知Angular可能有數(shù)據(jù)發(fā)生變化,需要檢測(cè)更新。
變化檢測(cè)原理
Angular得到需要重新檢查數(shù)據(jù)模型,更新視圖的通知后,是怎么執(zhí)行變化檢測(cè)的呢?答案是,臟檢查。考慮到還沒(méi)聽說(shuō)過(guò)臟檢查的同學(xué),這里解釋一下,臟檢查其實(shí)就是存儲(chǔ)所有變量的值,每當(dāng)可能有變量發(fā)生變化需要檢查時(shí),就將所有變量的舊值跟新值進(jìn)行比較,不相等就說(shuō)明檢測(cè)到變化,需要更新對(duì)應(yīng)視圖。
改善的臟檢查
接觸過(guò)AngularJS的同學(xué)肯定知道,它使用的變化檢測(cè)機(jī)制也是臟檢查。那么,同是臟檢查的背后,有何不同呢?為何Angular自稱變化檢測(cè)的性能比起AngularJS提升了很多?
Angular的核心是組件化,組件的嵌套會(huì)使得最終形成一棵組件樹。Angular的變化檢測(cè)可以分組件進(jìn)行,每個(gè)組件都有對(duì)應(yīng)的變化檢測(cè)器ChangeDetector。可想而知,這些變化檢測(cè)器也會(huì)構(gòu)成一棵樹。
另外,Angular的數(shù)據(jù)流是自頂而下,從父組件到子組件單向流動(dòng)。單向數(shù)據(jù)流向保證了高效、可預(yù)測(cè)的變化檢測(cè)。盡管檢查了父組件之后,子組件可能會(huì)改變父組件的數(shù)據(jù)使得父組件需要再次被檢查,這是不被推薦的數(shù)據(jù)處理方式。在開發(fā)模式下,Angular會(huì)進(jìn)行二次檢查,如果出現(xiàn)上述情況,二次檢查就會(huì)報(bào)錯(cuò):ExpressionChangedAfterItHasBeenCheckedError(關(guān)于這個(gè)問(wèn)題的答案,可以在參考資料中找到)。而在生產(chǎn)環(huán)境中,臟檢查只會(huì)執(zhí)行一次。
相比之下,AngularJS采用的是雙向數(shù)據(jù)流,錯(cuò)綜復(fù)雜的數(shù)據(jù)流使得它不得不多次檢查,使得數(shù)據(jù)最終趨向穩(wěn)定。理論上,數(shù)據(jù)可能永遠(yuǎn)不穩(wěn)定。AngularJS給出的策略是,臟檢查超過(guò)10次,就認(rèn)為程序有問(wèn)題,不再進(jìn)行檢查。這個(gè)10,我不知道它的給出依據(jù)是什么,也許是個(gè)經(jīng)驗(yàn)值吧。
變化檢測(cè)策略onPush
Angular還讓開發(fā)者擁有定制變化檢測(cè)策略的能力。
從ChangeDetectionStrategy可以看到,Angular有兩種變化檢測(cè)策略。Default是Angular默認(rèn)的變化檢測(cè)策略,也就是上述提到的臟檢查(只要有值發(fā)生變化,就全部檢查)。開發(fā)者可以根據(jù)場(chǎng)景來(lái)設(shè)置更加高效的變化檢測(cè)方式:onPush。onPush策略,就是只有當(dāng)輸入數(shù)據(jù)的引用發(fā)生變化或者有事件觸發(fā)時(shí),組件才進(jìn)行變化檢測(cè)。
比如上面這個(gè)例子,當(dāng)vData的屬性值發(fā)生變化的時(shí)候,這個(gè)組件不會(huì)發(fā)生變化檢測(cè),只有當(dāng)vData重新賦值的時(shí)候才會(huì)。一般,只接受輸入的木偶子組件(dumb components)比較適合采用onPush策略。
那什么時(shí)候只要對(duì)象的屬性值發(fā)生變化,整個(gè)對(duì)象的引用就變了呢?不可變對(duì)象(Immutable Object)。當(dāng)組件中的輸入對(duì)象是不變量時(shí),可采用onPush變化檢測(cè)策略,減少變化檢測(cè)的頻率。換個(gè)角度來(lái)說(shuō),為了更加智能地執(zhí)行變化檢測(cè),可以在只接受輸入的子組件中采用onPush策略。
變化檢測(cè)對(duì)象引用
Angular不僅可以讓開發(fā)者設(shè)置變化檢測(cè)的策略,還可以讓開發(fā)者獲取變化檢測(cè)對(duì)象引用ChangeDetectorRef,手動(dòng)去操作變化檢測(cè)。變化檢測(cè)對(duì)象引用給開發(fā)者提供的方法有以下幾種:
- markForCheck():將檢查組件的所有父組件所有子組件,即使設(shè)置了變化檢測(cè)策略為onPush
- detach():將變化檢測(cè)對(duì)象脫離檢測(cè)對(duì)象樹,不再進(jìn)行變化檢查;結(jié)合detectChanges可實(shí)現(xiàn)局部變化檢測(cè)
- detectChanges():將檢測(cè)該組件及其子組件,結(jié)合detach可實(shí)現(xiàn)局部檢測(cè)
- checkNoChanges(): 檢測(cè)該組件及其子組件,如果有變化存在則報(bào)錯(cuò),用于開發(fā)階段二次驗(yàn)證變化已經(jīng)完成
- reattach():將脫離的變化檢測(cè)對(duì)象重新鏈接到變化檢測(cè)樹上
那么,如果是Observable的話,它會(huì)訂閱所有的變量變化,只要在訂閱回調(diào)函數(shù)中手動(dòng)觸發(fā)變化檢測(cè)即可實(shí)現(xiàn)最小成本的檢測(cè)(仍采用onPush變化檢測(cè)策略)。舉個(gè)例子:
另外,當(dāng)數(shù)據(jù)模型變化太過(guò)頻繁,我們可自定義變化檢測(cè)的時(shí)機(jī)。舉個(gè)例子:
一個(gè)注意點(diǎn)是,采用onPush策略之后的組件detach()無(wú)效,具體可參考這里。
疑惑點(diǎn)
在Angular源碼中看到變化檢測(cè)對(duì)象有如下幾種狀態(tài):?
+?CheckOnce:表示只檢查一次,調(diào)用detectChanges之后狀態(tài)將會(huì)變?yōu)镃hecked?
+?Checked:表示在狀態(tài)變?yōu)镃heckOnce之前會(huì)跳過(guò)所有檢測(cè)?
+?CheckAlways:表示總是接受變化檢測(cè),每次調(diào)用detectChanges后狀態(tài)還是CheckAlways?
+?Detached:代表變化檢測(cè)對(duì)象脫離了變化檢測(cè)對(duì)象樹,不再進(jìn)行變化檢測(cè)?
+?Errored:表述變化測(cè)試對(duì)象發(fā)生錯(cuò)誤,變化檢測(cè)實(shí)效?
+?Destroyed:表示變化檢測(cè)對(duì)象已經(jīng)被銷毀
而OnPush策略表示變化檢測(cè)對(duì)象的狀態(tài)為CheckOnce。那么設(shè)置OnPush策略的組件為什么是引用發(fā)生變化之后才會(huì)執(zhí)行變化檢測(cè)的?檢測(cè)之后狀態(tài)從CheckOnce變成Checked,然后是如何變成CheckOnce的?
P.S. 嘗試閱讀Angular變化檢測(cè)這部分的源代碼,實(shí)在不知從何下手,網(wǎng)上資料甚少,求閱讀源碼經(jīng)驗(yàn)分享。
總結(jié)
Angular與AngularJS都采用臟檢查的變化檢測(cè)機(jī)制,前者優(yōu)于后者主要體現(xiàn)在:
- 單向數(shù)據(jù)流向
- 以組件為單位維度獨(dú)立進(jìn)行
- 生產(chǎn)環(huán)境只進(jìn)行一次檢查
- 可自定義的變化檢測(cè)策略:?Default和onPush
- 可自定義的變化檢測(cè)操作:markForcheck(),detectChanges(),detach(),?reattach(),checkNoChanges()
- 代碼實(shí)現(xiàn)上的優(yōu)化,據(jù)說(shuō)采用了VM friendly的代碼(這點(diǎn)我也不太明白,就隨便提一下)
參考資料:
- 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
總結(jié)
以上是生活随笔為你收集整理的Angular变化检测机制:改善的脏检查的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 懂球帝怎么买彩票
- 下一篇: Webstrom 卡顿问题