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