摘要:?jiǎn)蜗驍?shù)據(jù)流向保證了高效可預(yù)測(cè)的變化檢測(cè)。變化檢測(cè)策略有兩種變化檢測(cè)策略。另一種更加高效的變化檢測(cè)方式。策略,就是只有當(dāng)輸入數(shù)據(jù)即的引用發(fā)生變化或者有事件觸發(fā)時(shí),組件才進(jìn)行變化檢測(cè)。
概述
什么情況下會(huì)引起變化檢測(cè)?簡(jiǎn)單來(lái)說(shuō)變化檢測(cè)就是Angular用來(lái)檢測(cè)視圖與模型之間綁定的值是否發(fā)生了改變,當(dāng)檢測(cè)到模型中綁定的值發(fā)生改變時(shí),則同步到視圖上,反之,當(dāng)檢測(cè)到視圖上綁定的值發(fā)生改變時(shí),則回調(diào)對(duì)應(yīng)的綁定函數(shù)。
總結(jié)起來(lái), 主要有如下幾種情況可能也改變數(shù)據(jù):
用戶輸入操作,比如點(diǎn)擊,提交等
請(qǐng)求服務(wù)端數(shù)據(jù)(XHR)
定時(shí)事件,比如setTimeout,setInterval
上述三種情況都有一個(gè)共同點(diǎn),即這些導(dǎo)致綁定值發(fā)生改變的事件都是異步發(fā)生的。如果這些異步的事件在發(fā)生時(shí)能夠通知到Angular框架,那么Angular框架就能及時(shí)的檢測(cè)到變化。
左邊表示將要運(yùn)行的代碼,這里的stack表示Javascript的運(yùn)行棧,而webApi則是瀏覽器中提供的一些Javascript的API,TaskQueue表示Javascript中任務(wù)隊(duì)列,因?yàn)?b>Javascript是單線程的,異步任務(wù)在任務(wù)隊(duì)列中執(zhí)行。
具體來(lái)說(shuō),異步執(zhí)行的運(yùn)行機(jī)制如下:
所有同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧(execution context stack)。
主線程之外,還存在一個(gè)"任務(wù)隊(duì)列"(task queue)。只要異步任務(wù)有了運(yùn)行結(jié)果,就在"任務(wù)隊(duì)列"之 中放置一個(gè)事件。
一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取"任務(wù)隊(duì)列",看看里面有哪些事件。那些對(duì)應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧,開(kāi)始執(zhí)行。
主線程不斷重復(fù)上面的第三步。
當(dāng)上述代碼在Javascript中執(zhí)行時(shí),首先func1 進(jìn)入運(yùn)行棧,func1執(zhí)行完畢后,setTimeout進(jìn)入運(yùn)行棧,執(zhí)行setTimeout過(guò)程中將回調(diào)函數(shù)cb 加入到任務(wù)隊(duì)列,然后setTimeout出棧,接著執(zhí)行func2函數(shù),func2函數(shù)執(zhí)行完畢時(shí),運(yùn)行棧為空,接著任務(wù)隊(duì)列中cb 進(jìn)入運(yùn)行棧得到執(zhí)行。可以看出異步任務(wù)首先會(huì)進(jìn)入任務(wù)隊(duì)列,當(dāng)運(yùn)行棧中的同步任務(wù)都執(zhí)行完畢時(shí),異步任務(wù)進(jìn)入運(yùn)行棧得到執(zhí)行。如果這些異步的任務(wù)執(zhí)行前與執(zhí)行后能提供一些鉤子函數(shù),通過(guò)這些鉤子函數(shù),Angular便能獲知異步任務(wù)的執(zhí)行。
angular2 獲取變化通知那么問(wèn)題來(lái)了,angular2是如何知道數(shù)據(jù)發(fā)生了改變?又是如何知道需要修改DOM的位置,準(zhǔn)確的最小范圍的修改DOM呢?沒(méi)錯(cuò),盡可能小的范圍修改DOM,因?yàn)椴僮鱀OM對(duì)于性能來(lái)說(shuō)可是一件奢侈品。
在AngularJS中是由代碼$scope.$apply()或者$scope.$digest觸發(fā),而Angular接入了ZoneJS,由它監(jiān)聽(tīng)了Angular所有的異步事件。
ZoneJS是怎么做到的呢?
實(shí)際上Zone有一個(gè)叫猴子補(bǔ)丁的東西。在Zone.js運(yùn)行時(shí),就會(huì)為這些異步事件做一層代理包裹,也就是說(shuō)Zone.js運(yùn)行后,調(diào)用setTimeout、addEventListener等瀏覽器異步事件時(shí),不再是調(diào)用原生的方法,而是被猴子補(bǔ)丁包裝過(guò)后的代理方法。代理里setup了鉤子函數(shù), 通過(guò)這些鉤子函數(shù), 可以方便的進(jìn)入異步任務(wù)執(zhí)行的上下文.
//以下是Zone.js啟動(dòng)時(shí)執(zhí)行邏輯的抽象代碼片段 function zoneAwareAddEventListener() {...} function zoneAwareRemoveEventListener() {...} function zoneAwarePromise() {...} function patchTimeout() {...} window.prototype.addEventListener=zoneAwareAddEventListener; window.prototype.removeEventListener=zoneAwareRemoveEventListener; window.prototype.promise = zoneAwarePromise; window.prototype.setTimeout = patchTimeout;變化檢測(cè)的過(guò)程
Angular的核心是組件化,組件的嵌套會(huì)使得最終形成一棵組件樹(shù)。Angular的變化檢測(cè)可以分組件進(jìn)行,每一個(gè)Component都對(duì)應(yīng)有一個(gè)changeDetector,我們可以在Component中通過(guò)依賴注入來(lái)獲取到changeDetector。而我們的多個(gè)Component是一個(gè)樹(shù)狀結(jié)構(gòu)的組織,由于一個(gè)Component對(duì)應(yīng)一個(gè)changeDetector,那么changeDetector之間同樣是一個(gè)樹(shù)狀結(jié)構(gòu)的組織.
另外,Angular的數(shù)據(jù)流是自頂而下,從父組件到子組件單向流動(dòng)。單向數(shù)據(jù)流向保證了高效、可預(yù)測(cè)的變化檢測(cè)。盡管檢查了父組件之后,子組件可能會(huì)改變父組件的數(shù)據(jù)使得父組件需要再次被檢查,這是不被推薦的數(shù)據(jù)處理方式。在開(kāi)發(fā)模式下,Angular會(huì)進(jìn)行二次檢查,如果出現(xiàn)上述情況,二次檢查就會(huì)報(bào)錯(cuò):Expression Changed After It Has Been Checked Error。而在生產(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)行檢查。
變化檢測(cè)策略defalut 策略Angular有兩種變化檢測(cè)策略。Default是Angular默認(rèn)的變化檢測(cè)策略,也就是上述提到的臟檢查,只要有值發(fā)生變化,就全部從父組件到所有子組件進(jìn)行檢查,。另一種更加高效的變化檢測(cè)方式:OnPush。OnPush策略,就是只有當(dāng)輸入數(shù)據(jù)(即@Input)的引用發(fā)生變化或者有事件觸發(fā)時(shí),組件才進(jìn)行變化檢測(cè)。
main.component.ts
@Component({ selector: "app-root", template: `變更檢測(cè)策略
{{ slogan }}
`, }) export class AppComponent { slogan: string = "change detection"; title: string = "default 策略"; star: Star = new Star("周", "杰倫"); changeStar() { this.star.firstName = "吳"; this.star.lastName = "彥祖"; } changeStarObject() { this.star = new Star("劉", "德華"); } }
movie.component.ts
@Component({ selector: "movie", styles: ["div {border: 1px solid black}"], template: ``, }) export class MovieComponent { @Input() title: string; @Input() star; }{{ title }}
{{star.firstName}} {{star.lastName}}
上面代碼中, 當(dāng)點(diǎn)擊第一個(gè)按鈕改變明星屬性時(shí),依次對(duì)slogan, title, star三個(gè)屬性進(jìn)行檢測(cè), 此時(shí)三個(gè)屬性都沒(méi)有變化, star沒(méi)有發(fā)生變化,是因?yàn)閷?shí)質(zhì)上在對(duì)star檢測(cè)時(shí)只檢測(cè)star本身的引用值是否發(fā)生了改變,改變star的屬性值并未改變star本身的引用,因此是沒(méi)有發(fā)生變化。
而當(dāng)我們點(diǎn)擊第二個(gè)按鈕改變明星對(duì)象時(shí) ,重新new了一個(gè) star ,這時(shí)變化檢測(cè)才會(huì)檢測(cè)到 star發(fā)生了改變。
然后變化檢測(cè)進(jìn)入到子組件中,檢測(cè)到star.firstName和star.lastName發(fā)生了變化, 然后更新視圖.
OnPush策略與上面代碼相比, 只在movie.component.ts中的@component中增加了一行代碼:
changeDetection:ChangeDetectionStrategy.OnPush
此時(shí), 當(dāng)點(diǎn)擊第一個(gè)按鈕時(shí), 檢測(cè)到star沒(méi)有發(fā)生變化, ok,變化檢測(cè)到此結(jié)束, 不會(huì)進(jìn)入到子組件中, 視圖不會(huì)發(fā)生變化.
當(dāng)點(diǎn)擊第二個(gè)按鈕時(shí),檢測(cè)到star發(fā)生了變化, 然后變化檢測(cè)進(jìn)入到子組件中,檢測(cè)到star.firstName和star.lastName發(fā)生了變化, 然后更新視圖.
所以,當(dāng)你使用了OnPush檢測(cè)機(jī)制時(shí),在修改一個(gè)綁定值的屬性時(shí),要確保同時(shí)修改到了綁定值本身的引用。但是每次需要改變屬性值的時(shí)候去new一個(gè)新的對(duì)象會(huì)很麻煩,immutable.js 你值得擁有!
變化檢測(cè)對(duì)象引用通過(guò)引用變化檢測(cè)對(duì)象ChangeDetectorRef,可以手動(dòng)去操作變化檢測(cè)。我們可以在組件中的通過(guò)依賴注入的方式來(lái)獲取該對(duì)象:
constructor( private changeRef:ChangeDetectorRef ){}
變化檢測(cè)對(duì)象提供的方法有以下幾種:
markForCheck() - 在組件的 metadata 中如果設(shè)置了 changeDetection:ChangeDetectionStrategy.OnPush 條件,那么變化檢測(cè)不會(huì)再次執(zhí)行,除非手動(dòng)調(diào)用該方法, 該方法的意思是在變化監(jiān)測(cè)時(shí)必須檢測(cè)該組件。
detach() - 從變化檢測(cè)樹(shù)中分離變化檢測(cè)器,該組件的變化檢測(cè)器將不再執(zhí)行變化檢測(cè),除非手動(dòng)調(diào)用 reattach() 方法。
reattach() - 重新添加已分離的變化檢測(cè)器,使得該組件及其子組件都能執(zhí)行變化檢測(cè)
detectChanges() - 從該組件到各個(gè)子組件執(zhí)行一次變化檢測(cè)
OnPush策略下手動(dòng)發(fā)起變化檢測(cè)
組件中添加事件改變輸入屬性
在上面代碼movie.component.ts中修改如下
@Component({ selector: "movie", styles: ["div {border: 1px solid black}"], template: ``, changeDetection:ChangeDetectionStrategy.OnPush }) export class MovieComponent { constructor( private changeRef:ChangeDetectorRef ){} @Input() title: string; @Input() star; changeStar(){ this.star.lastName = "xjl"; } }{{ title }}
{{star.firstName}} {{star.lastName}}
此時(shí)點(diǎn)擊按鈕切換名字時(shí),star更改如下
![圖片描述][3]
第二種就是上面講到的使用變化檢測(cè)對(duì)象中的 markForCheck()方法.
ngOnInit() { setInterval(() => { this.star.lastName = "xjl"; this.changeRef.markForCheck(); }, 1000); }輸入屬性為Observable
修改app.component.ts
@Component({ selector: "app-root", template: `變更檢測(cè)策略
{{ slogan }}
`, }) export class AppComponent implements OnInit{ slogan: string = "change detection"; title: string = "OnPush 策略"; star: Star = new Star("周", "杰倫"); count:Observable ; ngOnInit(){ this.count = Observable.timer(0, 1000) } changeStar() { this.star.firstName = "吳"; this.star.lastName = "彥祖"; } changeStarObject() { this.star = new Star("劉", "德華"); } }
此時(shí),有兩種方式讓MovieComponent進(jìn)入檢測(cè),一種是使用變化檢測(cè)對(duì)象中的 markForCheck()方法.
ngOnInit() { this.addCount.subscribe(() => { this.count++; this.changeRef.markForCheck(); })
另外一種是使用async pipe 管道
@Component({ selector: "movie", styles: ["div {border: 1px solid black}"], template: ``, changeDetection: ChangeDetectionStrategy.OnPush }){{ title }}
{{star.firstName}} {{star.lastName}}
{{addCount | async}}
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/85071.html
摘要:編寫(xiě)工作首先介紹了一個(gè)稱為的內(nèi)部組件表示,并解釋了變更檢測(cè)過(guò)程在視圖上運(yùn)行。本文主要由兩部分組成第一部分探討錯(cuò)誤產(chǎn)生的原因,第二部分提出可能的修正。它對(duì)我意義重大,它能幫助其他人看到這篇文章。 在過(guò)去的8個(gè)月里,我大部分空閑時(shí)間都是reverse-engineering Angular。我最感興趣的話題是變化檢測(cè)。我認(rèn)為它是框架中最重要的部分,因?yàn)樗?fù)責(zé)像DOM更新、輸入綁定和查詢列表...
摘要:但如果一個(gè)組件在生命周期鉤子里改變父組件屬性,卻是可以的,因?yàn)檫@個(gè)鉤子函數(shù)是在更新父組件屬性變化之前調(diào)用的注即第步,在第步之前調(diào)用。 原文鏈接:Angular.js’ $digest is reborn in the newer version of Angular showImg(https://segmentfault.com/img/remote/146000001468785...
摘要:策略減少檢測(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.j...
摘要:如果我們不使用提供的事件系統(tǒng)定時(shí)器和,如在事件中進(jìn)行數(shù)據(jù)更新時(shí),我們需要手動(dòng)調(diào)用。 前言 在傳統(tǒng)的WEB開(kāi)發(fā)中,當(dāng)與用戶或服務(wù)器發(fā)生交互時(shí),需要我們手動(dòng)獲取數(shù)據(jù)并更新DOM,這個(gè)過(guò)程是繁瑣的、易錯(cuò)的。特別是當(dāng)頁(yè)面功能過(guò)于復(fù)雜時(shí),我們既要關(guān)注數(shù)據(jù)的變化,又要維護(hù)DOM的更新,這樣寫(xiě)出來(lái)的代碼是很難維護(hù)的。新一代的框架或庫(kù),例如Angular、React、Vue等等讓我們的關(guān)注點(diǎn)只在數(shù)據(jù)上...
摘要:最近接手了一個(gè)項(xiàng)目,客戶提出了一個(gè)高大上的需求要求只有一個(gè)主界面,所有組件通過(guò)來(lái)顯示。 最近接手了一個(gè)項(xiàng)目,客戶提出了一個(gè)高大上的需求:要求只有一個(gè)主界面,所有組件通過(guò)Tab來(lái)顯示。其實(shí)這個(gè)需求并不詭異,不喜歡界面跳轉(zhuǎn)的客戶都非常熱衷于這種展現(xiàn)形式。 好吧,客戶至上,搞定它!這種實(shí)現(xiàn)方式在傳統(tǒng)的HTML應(yīng)用中,非常簡(jiǎn)單,只是在這Angular4(以下簡(jiǎn)稱ng)中,咋個(gè)弄呢? 我們先來(lái)了...
閱讀 2001·2023-04-25 16:19
閱讀 3116·2021-11-24 09:39
閱讀 837·2021-11-16 11:44
閱讀 1699·2019-08-29 12:52
閱讀 1147·2019-08-26 13:33
閱讀 1081·2019-08-26 10:26
閱讀 2209·2019-08-23 16:42
閱讀 2574·2019-08-23 14:37