摘要:策略減少檢測(cè)次數(shù)當(dāng)輸入屬性不變時(shí),可以跳過(guò)整個(gè)變更檢測(cè)子樹(shù)?,F(xiàn)在當(dāng)執(zhí)行更改檢測(cè)時(shí),它將從上到下進(jìn)行。并且一旦更改檢測(cè)運(yùn)行結(jié)束,它將恢復(fù)整個(gè)樹(shù)的狀態(tài)。
Angular 2.x+ 臟檢查機(jī)制理解
目前幾種主流的前端框架都已經(jīng)實(shí)現(xiàn)雙向綁定特性,但實(shí)現(xiàn)的方法各有不同:
發(fā)布者-訂閱者模式(backbone.js)
臟值檢查(angular.js)
數(shù)據(jù)劫持 + 發(fā)布者-訂閱者模式(vue.js)
下面我們就來(lái)了解一下 ng2.x+ 的版本中的臟檢查機(jī)制是如何運(yùn)行的。
什么是變化檢測(cè)變化檢測(cè)(臟檢查)的基本任務(wù)是獲取程序內(nèi)部狀態(tài)的變化,并使其在用戶(hù)界面上以某種方式可見(jiàn),這種狀態(tài)的變化可以來(lái)自于 JavaScript 的任何數(shù)據(jù)結(jié)構(gòu),最終呈現(xiàn)為用戶(hù)界面中的段落、表單、鏈接或者按鈕等 DOM 對(duì)象。我們把輸入數(shù)據(jù)結(jié)構(gòu)并生成 DOM 結(jié)構(gòu)顯示給用戶(hù)的過(guò)程叫作渲染。什么會(huì)引起變化然而在程序運(yùn)行時(shí)發(fā)生變化情況比較復(fù)雜,我們需要確定模型中發(fā)生什么變化,以及什么地方需要更新 DOM 節(jié)點(diǎn)。操作 DOM 樹(shù)十分昂貴,所以我們不僅需要找出待更新的地方,還需要保持操作數(shù)盡可能小。這可能通過(guò)許多不同的方式來(lái)解決:比如我們可以簡(jiǎn)單的發(fā)起 http 請(qǐng)求并重新渲染整個(gè)頁(yè)面,或者可以區(qū)分 DOM 樹(shù)的新舊狀態(tài)并只重新渲染二者不同的部分(ReactJS 虛擬 DOM 的解決方案)。
什么時(shí)候會(huì)產(chǎn)生變化?
Angular 如何確知更新視圖的時(shí)機(jī)?
@Component({ template: `{{firstname}} {{lastname}}
` }) class MyApp { firstname:string = "Pascal"; lastname:string = "Precht"; changeName() { this.firstname = "Brad"; this.lastname = "Green"; } }
上面的組件只是顯示兩個(gè)屬性,并提供一個(gè)方法來(lái)改變他們(點(diǎn)擊模板中的按鈕),點(diǎn)擊這個(gè)特定按鈕的時(shí)刻即是應(yīng)用程序狀態(tài)發(fā)生改變的時(shí)刻,因?yàn)樗淖兘M件的屬性,這也是我們想要更新視圖的時(shí)刻。
@Component() class ContactsApp implements OnInit{ contacts:Contact[] = []; constructor(private http: Http) {} ngOnInit() { this.http.get("/contacts") .map(res => res.json()) .subscribe(contacts => this.contacts = contacts); } }
這個(gè)組件擁有一個(gè)聯(lián)系人列表,當(dāng)它初始化時(shí)發(fā)送一個(gè) http 請(qǐng)求,一旦這個(gè)請(qǐng)求返回列表就會(huì)被更新,此時(shí)我們的應(yīng)用程序狀態(tài)發(fā)生改變,并需要更新視圖。基本上應(yīng)用程序狀態(tài)的改變可以由三類(lèi)活動(dòng)引起:
事件 - click, submit, ...
XHR - 從遠(yuǎn)程服務(wù)器獲取數(shù)據(jù)
定時(shí)器 - setTimeout, setInterval
上述活動(dòng)都是異步的,因此我們可以得出結(jié)論:每當(dāng)執(zhí)行一些異步操作時(shí),我們的應(yīng)用程序狀態(tài)可能發(fā)生改變,這時(shí)則需要 Angular 更新視圖。Angular 在啟動(dòng)時(shí)會(huì)重寫(xiě)(通過(guò) Zone.js)部分底層瀏覽器 APIs 比如 addEventListener:
// this is the new version of addEventListener function addEventListener(eventName, callback) { // call the real addEventListener callRealAddEventListener(eventName, function() { // first call the original callback callback(...); // and then run Angular-specific functionality var changed = angular2.runChangeDetection(); if (changed) { angular2.reRenderUIPart(); } }); }誰(shuí)通知 Angular 更新視圖
Zone 負(fù)責(zé)通知 Angular 進(jìn)行視圖更新,Angular 封裝有 NgZone,簡(jiǎn)單來(lái)說(shuō),通過(guò) Angular 的部分源碼我們可以知道有一個(gè)叫作 ApplicationRef 的東西負(fù)責(zé)監(jiān)聽(tīng) NgZone 中的 onTurnDone 事件,每當(dāng)該事件觸發(fā)時(shí),它就執(zhí)行 trick 方法進(jìn)行變化檢測(cè)的基本工作。
// very simplified version of actual source class ApplicationRef { changeDetectorRefs:ChangeDetectorRef[] = []; constructor(private zone: NgZone) { this.zone.onTurnDone .subscribe(() => this.zone.run(() => this.tick()); } tick() { this.changeDetectorRefs .forEach((ref) => ref.detectChanges()); } }變化檢測(cè)
首先我們需要注意的是在 Angular 中每個(gè)組件都有自己的變化檢測(cè)器,這使得我們可以對(duì)每個(gè)組件分別控制如何以及何時(shí)進(jìn)行變化檢測(cè)。
由于每個(gè)組件都有其自己的變化檢測(cè)器,即一個(gè) Angular 應(yīng)用程序由一個(gè)組件樹(shù)組成,所以邏輯結(jié)果就是我們也有一個(gè)變化檢測(cè)器樹(shù),這棵樹(shù)也可以看作是一個(gè)有向圖,數(shù)據(jù)總是從上到下流動(dòng)。數(shù)據(jù)從上到下的原因是因?yàn)樽兓瘷z測(cè)也總是從上到下對(duì)每一個(gè)多帶帶的組件進(jìn)行,每一次從根組件開(kāi)始,單向數(shù)據(jù)流比循環(huán)臟檢查更可預(yù)測(cè),我們總是可以知道視圖中使用的數(shù)據(jù)來(lái)自哪里。
我們假設(shè)在組件樹(shù)的某個(gè)地方觸發(fā)一個(gè)事件,比如一個(gè)按鈕被點(diǎn)擊,zones 會(huì)進(jìn)行事件的處理并通知 Angular,然后變化檢測(cè)依次向下傳遞。如何觸發(fā)變化檢測(cè)
一種方法是基于組件的生命周期鉤子:
ngAfterViewChecked() { if (this.callback && this.clicked) { console.log("changing status ..."); this.callback(Math.random()); } }
在開(kāi)發(fā)模式下運(yùn)行 Angular 會(huì)在控制臺(tái)中得到一條錯(cuò)誤日志,生產(chǎn)模式下則不會(huì)拋出。
EXCEPTION: Expression "{{message}} in App@3:20" has changed after it was checked
另一種方法是手動(dòng)控制變化檢測(cè)的打開(kāi)/關(guān)閉,并手動(dòng)觸發(fā):
constructor(private ref: ChangeDetectorRef) { ref.detach(); setInterval(() => { this.ref.detectChanges(); }, 5000); }改善的臟檢查
Angular 2.x+ 的數(shù)據(jù)流是自頂向下,從父組件向子組件的的單向流動(dòng),變化監(jiān)測(cè)樹(shù)與之相呼應(yīng),單項(xiàng)數(shù)據(jù)量保證變化監(jiān)測(cè)的高效性和可預(yù)測(cè)性。檢查父組件后,子組件可能會(huì)改變父組件中的數(shù)據(jù)使得父組件需要被再次檢查,這是不被推薦的數(shù)據(jù)處理方式,并且在開(kāi)發(fā)模式下這種情況會(huì)拋出異常 ExpressionChangedAfterItHasBeenCheckedError,在生產(chǎn)模式下不會(huì)報(bào)錯(cuò)但是臟檢查僅會(huì)執(zhí)行一次。性能相比之下 1.x 的版本采用雙向數(shù)據(jù)流,為了使得數(shù)據(jù)最終趨向穩(wěn)定不得不多次檢查錯(cuò)綜復(fù)雜的數(shù)據(jù)流,性能提升就此可見(jiàn)一斑。
默認(rèn)情況下,即使每次發(fā)生事件都需要檢查每個(gè)組件,Angular 速度仍然非???,它可以在幾毫秒內(nèi)執(zhí)行數(shù)十萬(wàn)次檢查,這主要是由于 Angular 可以生成 VM 友好的代碼。更優(yōu)的變化檢測(cè)
Angular 每次都要檢查每個(gè)組件,因?yàn)槭录l(fā)生的原因也許是應(yīng)用程序狀態(tài)已經(jīng)改變,但是如果我們能夠告訴 Angular 只對(duì)那些改變狀態(tài)的應(yīng)用程序部分運(yùn)行變化檢測(cè),那不是很好嗎?理解不可變事實(shí)證明,有些數(shù)據(jù)結(jié)構(gòu)可以給我們什么時(shí)候發(fā)生變化的一些保證 - Immutables 和 Observables。
比如我們擁有一個(gè)組件 VCardApp 使用 v-card 作為子組件,其具有一個(gè)輸入屬性 vData,并且我們可以使用 changeData 方法改變 vData 對(duì)象的 name 屬性(并不會(huì)改變?cè)搶?duì)象的引用)。
@Component({ template: "" }) class VCardApp { constructor() { this.vData = { name: "Christoph Burgdorf", email: "christoph@thoughtram.io" } } changeData() { this.vData.name = "Pascal Precht"; } }
當(dāng)某些事件導(dǎo)致 changeData 執(zhí)行時(shí),vData.name 發(fā)生改變并傳遞至 v-card 中,v-card 組件的變化檢測(cè)器檢查給定的數(shù)據(jù)新 vData 是否與以前一樣,在數(shù)據(jù)引用未變但是其參數(shù)改變的情況下,Angular 也需要對(duì)該數(shù)據(jù)進(jìn)行變化監(jiān)測(cè)。這就是 immutable 數(shù)據(jù)結(jié)構(gòu)發(fā)揮作用的地方。
[
How I optimized Minesweeper using Angular 2 and Immutable.js to make it insanely fast](https://www.jvandemo.com/how-...
Immutable 為我們提供不可變的對(duì)象:這意味著如果我們使用不可變的對(duì)象,并且想要對(duì)這樣的對(duì)象進(jìn)行更改,我們會(huì)得到一個(gè)新的引用(保證原始對(duì)象不變)。
var vData = someAPIForImmutables.create({ name: "Pascal Precht" }); var vData2 = vData.set("name", "Christoph Burgdorf"); vData === vData2 // false
上述偽代碼即演示不可變對(duì)象的含義,其中 someAPIForImmutables 可以是我們想要用于不可變數(shù)據(jù)結(jié)構(gòu)的任何 API。OnPush 策略減少檢測(cè)次數(shù)
當(dāng)輸入屬性不變時(shí),Angular可以跳過(guò)整個(gè)變更檢測(cè)子樹(shù)。如果我們?cè)?Angular 應(yīng)用程序中使用不可變對(duì)象,我們所需要做的就是告訴 Angular 組件可以跳過(guò)變化檢測(cè),如果它的輸入沒(méi)有改變的話(huà)。
@Component({ template: `{{vData.name}}
{{vData.email}} ` }) class VCardCmp { @Input() vData; }
正如我們所看到的,VCardCmp 只依賴(lài)于它的輸入屬性,我們可以告訴 Angular 跳過(guò)這個(gè)組件的子樹(shù)的變化檢測(cè),如果它的輸入沒(méi)有改變,通過(guò)設(shè)置變化檢測(cè)策略 OnPush 是這樣的:
@Component({ template: `{{vData.name}}
{{vData.email}} `, changeDetection: ChangeDetectionStrategy.OnPush }) class VCardCmp { @Input() vData; }
Angular OnPush Change Detection and Component Design - Avoid Common Pitfalls
Observables與不可變的對(duì)象不同,當(dāng)進(jìn)行更改時(shí) Observables 不會(huì)給我們提供新的引用,而是發(fā)射我們可以訂閱的事件來(lái)對(duì)他們做出反應(yīng)。
@Component({ template: "{{counter}}", changeDetection: ChangeDetectionStrategy.OnPush }) class CartBadgeCmp { @Input() addItemStream:Observable; counter = 0; ngOnInit() { this.addItemStream.subscribe(() => { this.counter++; // application state changed }) } }
比如我們用購(gòu)物車(chē)建立一個(gè)電子商務(wù)應(yīng)用程序:每當(dāng)用戶(hù)將產(chǎn)品放入購(gòu)物車(chē)時(shí),我們需要在用戶(hù)界面中顯示一個(gè)小計(jì)數(shù)器,以便用戶(hù)可以看到購(gòu)物車(chē)中的產(chǎn)品數(shù)量。該組件有一個(gè) counter 屬性和一個(gè)輸入屬性 addItemStream,當(dāng)產(chǎn)品被添加到購(gòu)物車(chē)時(shí),這是一個(gè)被觸發(fā)的事件流。另外,我們?cè)O(shè)置了變化檢測(cè)策略 OnPush,只有當(dāng)組件的輸入屬性發(fā)生變化時(shí),變化檢測(cè)才會(huì)執(zhí)行。
如前所述,引用 addItemStream 永遠(yuǎn)不會(huì)改變,所以組件的子樹(shù)從不執(zhí)行變更檢測(cè)。
當(dāng)整個(gè)樹(shù)被設(shè)置成 OnPush 后,我們?nèi)绾瓮ㄖ?Angular 需要對(duì)這個(gè)組件進(jìn)行變化檢測(cè)呢?正如我們所知,變化檢測(cè)總是從上到下執(zhí)行的,所以我們需要的是一種可以檢測(cè)樹(shù)的整個(gè)路徑到發(fā)生變化的組件的變化的方法。我們可以通過(guò)依賴(lài)注入訪問(wèn)組件的 ChangeDetectorRef,這個(gè)注入來(lái)自一個(gè)叫做 markForCheck 的 API,它標(biāo)記從組件到根的路徑,以便下次更改檢測(cè)的運(yùn)行。
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() { this.addItemStream.subscribe(() => { this.counter++; // application state changed this.cd.markForCheck(); // marks path }) } }
下面是在可觀察事件被觸發(fā)后,變化檢測(cè)開(kāi)始前。
現(xiàn)在當(dāng)執(zhí)行更改檢測(cè)時(shí),它將從上到下進(jìn)行。
并且一旦更改檢測(cè)運(yùn)行結(jié)束,它將恢復(fù) OnPush 整個(gè)樹(shù)的狀態(tài)。
TAKING ADVANTAGE OF OBSERVABLES IN ANGULAR
ANGULAR CHANGE DETECTION EXPLAINED
How does Angular Change Detection Really Work ?
Change And Its Detection In JavaScript Frameworks
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/83215.html
摘要:通常寫(xiě)代碼時(shí)我們無(wú)需主動(dòng)調(diào)用或是因?yàn)樵谕獠繉?duì)我們的回調(diào)函數(shù)做了包裝。類(lèi)似的不只是這些事件回調(diào)函數(shù),還有等。常量依舊會(huì)重復(fù)檢查。會(huì)檢查中有沒(méi)有一個(gè)名為的成員。 TL;DR 臟檢查是一種模型到視圖的數(shù)據(jù)映射機(jī)制,由 $apply 或 $digest 觸發(fā)。 臟檢查的范圍是整個(gè)頁(yè)面,不受區(qū)域或組件劃分影響 使用盡量簡(jiǎn)單的綁定表達(dá)式提升臟檢查執(zhí)行速度 盡量減少頁(yè)面上綁定表達(dá)式的個(gè)數(shù)(單次綁定...
摘要:通常寫(xiě)代碼時(shí)我們無(wú)需主動(dòng)調(diào)用或是因?yàn)樵谕獠繉?duì)我們的回調(diào)函數(shù)做了包裝。類(lèi)似的不只是這些事件回調(diào)函數(shù),還有等。常量依舊會(huì)重復(fù)檢查。會(huì)檢查中有沒(méi)有一個(gè)名為的成員。 TL;DR 臟檢查是一種模型到視圖的數(shù)據(jù)映射機(jī)制,由 $apply 或 $digest 觸發(fā)。 臟檢查的范圍是整個(gè)頁(yè)面,不受區(qū)域或組件劃分影響 使用盡量簡(jiǎn)單的綁定表達(dá)式提升臟檢查執(zhí)行速度 盡量減少頁(yè)面上綁定表達(dá)式的個(gè)數(shù)(單次綁定...
摘要:來(lái)源于社區(qū),時(shí)至今日已經(jīng)基本成為的標(biāo)配了。部分很簡(jiǎn)單,要根據(jù)傳入的執(zhí)行不同的操作。當(dāng)性能遇到瓶頸時(shí)基本不會(huì)遇到,可以更改,保證傳入數(shù)據(jù)來(lái)提升性能。當(dāng)不再能滿(mǎn)足程序開(kāi)發(fā)的要求時(shí),可以嘗試使用進(jìn)行函數(shù)式編程。 Immutable & Redux in Angular Way 寫(xiě)在前面 AngularJS 1.x版本作為上一代MVVM的框架取得了巨大的成功,現(xiàn)在一提到Angular,哪怕是已...
摘要:并不是真正的進(jìn)入,而是通過(guò)包裹的方式偽造執(zhí)行上下文,并通過(guò)鉤子函數(shù)方便的進(jìn)入執(zhí)行環(huán)境。如何使用運(yùn)行結(jié)果可以從上面的看到運(yùn)用提供的,鉤子函數(shù)方便的進(jìn)入了執(zhí)行的上下文,記錄了時(shí)間。我們還有個(gè)需求,需要因人而異的處理這些暴露的鉤子函數(shù)。 angular2 臟檢查總述 這系列文章將介紹angular2的臟值檢查是如何工作的?如何比ng1更高效?帶著上述問(wèn)題,讓我們一起來(lái)看看angular2這禽...
閱讀 2879·2023-04-25 15:01
閱讀 3188·2021-11-23 10:07
閱讀 3415·2021-10-12 10:12
閱讀 3528·2021-08-30 09:45
閱讀 2250·2021-08-20 09:36
閱讀 3661·2019-08-30 12:59
閱讀 2506·2019-08-26 13:52
閱讀 989·2019-08-26 13:24