您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“Angular中的變更實例檢測分析”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!
Angular 中的變更檢測是一種用來將應(yīng)用程序 UI 的狀態(tài)與數(shù)據(jù)的狀態(tài)同步的機制。當應(yīng)用邏輯更改組件數(shù)據(jù)時,綁定到視圖中 DOM 屬性上的值也要隨之更改。變更檢測器負責更新視圖以反映當前的數(shù)據(jù)模型。
紙上得來終覺淺,絕知此事要躬行。為了讓讀者朋友們更容易理解,本文先從一個小的示例入手,然后逐步展開。示例如下:
// app.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'aa'; handleClick() { this.title = 'bb'; }} // app.componnet.html <div (click)="handleClick()">{{title}}</div>
示例比較簡單,就是給div
元素綁定了一個點擊事件,點擊該元素就會改變變量title
的值,界面的顯示也會隨之更新??蚣苋绾沃朗裁磿r候需要更新視圖,以及如何更新視圖的呢?我們來一探究竟。
當我們點擊div
元素時,handleClick
函數(shù)會被執(zhí)行。那么在 Angular 應(yīng)用中該函數(shù)是如何被觸發(fā)執(zhí)行的呢?如果你看過我之前的關(guān)于zone.js
介紹的文章就會知道,Angular 應(yīng)用中點擊事件已經(jīng)被zone.js
接管?;诖舜鸢副泔@而易見,最開始肯定是被zone.js
觸發(fā)執(zhí)行,但在這里我還們還要進一步分析直接調(diào)用關(guān)系進而層層展開。最靠近handleClick
函數(shù)調(diào)用的是下面的代碼:
function wrapListener(listenerFn, ...) { return function wrapListenerIn_markDirtyAndPreventDefault(e) { let result = executeListenerWithErrorHandling(listenerFn, ...); } }
上述代碼中listenerFn
函數(shù)指向的便是handleClick
,但它又是wrapListener
函數(shù)的參數(shù)。示例中元素綁定點擊事件,相關(guān)模板編譯產(chǎn)物大概是這樣:
function AppComponent_Template(rf, ctx) { ...... i0["??listener"]("click", function AppComponent_Template_div_click_0_listener() { return ctx.handleClick(); }) }
初次加載應(yīng)用會依次執(zhí)行renderView
、然后執(zhí)行executeTemplate
,接著便觸發(fā)了上述的模板函數(shù),就這樣元素的點擊函數(shù)便一路傳遞到了listenerFn
參數(shù)。到這里我們了解了,點擊函數(shù)的觸發(fā)源頭是zone.js
,真實的點擊函數(shù)傳遞卻是由 Angular 實現(xiàn),那么zone.js
和 Angular 是如何關(guān)聯(lián)的呢?zone.js
會為每個異步事件安排一個任務(wù),結(jié)合本文示例來說,invokeTask
便是由下面代碼調(diào)用:
function forkInnerZoneWithAngularBehavior(zone) { zone._inner = zone._inner.fork({ name: 'angular', properties: { 'isAngularZone': true }, onInvokeTask: (delegate, current, target, task, applyThis, applyArgs) => { try { onEnter(zone); return delegate.invokeTask(target, task, ...); } finally { onLeave(zone); } } }) }
看到這里是不是就很熟悉了,因為在之前的zone.js
介紹的文章里,便有類似的代碼片段。而forkInnerZoneWithAngularBehavior
函數(shù)又是由類 NgZone 的構(gòu)造函數(shù)調(diào)用。至此我們引出了 Angular 變更檢測的一個主角 NgZone,它是對zone.js
的一個簡單封裝。
現(xiàn)在我們知道示例中點擊函數(shù)是如何被執(zhí)行的,那么函數(shù)執(zhí)行了以后應(yīng)用數(shù)據(jù)有變化了,視圖又是如何及時更新的呢?我們還是回到上面提到的forkInnerZoneWithAngularBehavior
函數(shù)中,try finally
語句塊中,執(zhí)行了invokeTask
函數(shù)最終還會執(zhí)行onLeave(zone)
函數(shù)。再往下分析就能看到onLeave
函數(shù)最終調(diào)用了checkStable
函數(shù):
function checkStable(zone) { zone.onMicrotaskEmpty.emit(null); }
相應(yīng)地在類ApplicationRef
構(gòu)造函數(shù)中訂閱了這個emit
事件:
class ApplicationRef { /** @internal */ constructor() { this._zone.onMicrotaskEmpty.subscribe({ next: () => { this._zone.run(() => { this.tick(); }); } }); }
在訂閱相關(guān)回調(diào)函數(shù)中,this.tick()
是不是很眼熟呢?如果你看了我之前的關(guān)于 Angular 生命周期函數(shù)的文章,那么你肯定還會有印象,它是觸發(fā)視圖更新的關(guān)鍵調(diào)用。雖然在那篇生命周期介紹的文章中有講過這個函數(shù),但本文的重點是變更檢測因此函數(shù)雖然相同但側(cè)重點略有變化。this.tick
相關(guān)調(diào)用順序大概是這樣:
this.tick() -> view.detectChanges() -> renderComponentOrTemplate() -> refreshView()
這里refreshView
比較重要單獨拿出來分析一下:
function refreshView(tView, lView, templateFn, context) { ...... if (templateFn !== null) { // 關(guān)鍵代碼1 executeTemplate(tView, lView, templateFn, ...); } ...... if (components !== null) { // 關(guān)鍵代碼2 refreshChildComponents(lView, components); } }
這個過程中refreshView
函數(shù)會被調(diào)用二次,第一次進入的是關(guān)鍵代碼2分支,然后依次調(diào)用如下函數(shù)重新進入refreshView
函數(shù):
refreshChildComponents() -> refreshChildComponents() -> refreshComponent() -> refreshView()
第二次進入refreshView
函數(shù)調(diào)用的便是關(guān)鍵代碼1分支了,即執(zhí)行的是:executeTemplate
函數(shù)。而該函數(shù)最終執(zhí)行的是模板編譯產(chǎn)物中的AppComponent_Template
函數(shù):
function AppComponent_Template(rf, ctx) { if (rf & 1) { // 條件分支1 i0["??elementStart"](0, "div", 0); i0["??listener"]("click", function AppComponent_Template_div_click_0_listener() { return ctx.handleClick(); }); i0["??text"](1); i0["??elementEnd"](); } if (rf & 2) { // 條件分支2 i0["??advance"](1); i0["??textInterpolate"](ctx.title); } }
如果還有讀者不清楚上述模板編譯產(chǎn)物中的函數(shù)是怎么來的,建議閱讀之前關(guān)于依賴注入原理講解的文章,因篇幅限制不再贅述。此時AppComponent_Template
函數(shù)執(zhí)行的是條件分支2里的代碼,??advance
函數(shù)作用是更新相關(guān)的索引值,以保證找到正確的元素。這里重點講講??textInterpolate
函數(shù),它最終調(diào)用的是函數(shù)??textInterpolate1
:
function ??textInterpolate1(prefix, v0, suffix) { const lView = getLView(); // 關(guān)鍵代碼1 const interpolated = interpolation1(lView, prefix, v0, suffix); if (interpolated !== NO_CHANGE) { // 關(guān)鍵代碼2 textBindingInternal(lView, getSelectedIndex(), interpolated); } return ??textInterpolate1; }
值得指出的是,該函數(shù)名末尾是數(shù)字1,這是因為還有類似的??textInterpolate2
、??textInterpolate3
等等,Angular 內(nèi)部根據(jù)插值表達式的數(shù)量調(diào)用不同的專用函數(shù),本文示例中文本節(jié)點的插值表達式數(shù)量為1,因此實際調(diào)用的是??textInterpolate1
函數(shù)。該函數(shù)主要做了兩件事,關(guān)鍵代碼1作用是比較插值表達式值有沒有更新,關(guān)鍵代碼2則是更新文本節(jié)點的值。先來看看關(guān)鍵代碼1的函數(shù)interpolation1
,它最終調(diào)用的是:
function bindingUpdated(lView, bindingIndex, value) { const oldValue = lView[bindingIndex]; if (Object.is(oldValue, value)) { return false; } else { lView[bindingIndex] = value; return true; } }
變更檢測前的文本節(jié)點值稱之為oldValue
, 該值存儲在lView
中,lView
我在之前的文章中也提到過,忘記了的讀者可以去看看lView
的作用。bindingUpdated
首先會比較新值和舊值,比較的方法便是Object.is
。如果新值舊值沒有變化,則返回false
。如果有變化,則更新lView
中存儲的值,并返回true
。關(guān)鍵代碼2的函數(shù)textBindingInternal
最終調(diào)用的是下述函數(shù):
function updateTextNode(renderer, rNode, value) { ngDevMode && ngDevMode.rendererSetText++; isProceduralRenderer(renderer) ? renderer.setValue(rNode, value) : rNode.textContent = value; }
走完上述流程,我們點擊div
元素時,界面顯示內(nèi)容便會由aa
變?yōu)?code>bb,即完成了從應(yīng)用數(shù)據(jù)的變更到 UI 狀態(tài)的同步更新,這便是 Angular 最基本的變更檢測過程了。
“Angular中的變更實例檢測分析”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。