angular如何進行效能優化?變更檢測方式淺析

2022-07-06 22:00:57
如何進行效能優化?下面本篇文章給大家深入介紹一下angular 效能優化方案--變更檢測,希望對大家有所幫助!

angular 效能優化——變更檢測

對於前端效能指標描述,業界都各有說詞,總結下來都和首屏效能和頁面流暢度相關, 本次將會從頁面流暢度的角度,對頁面互動效能優化進行分析。【相關教學推薦:《》】

什麼是頁面流暢度?

頁面流暢度是通過影格率 FPS(Frames Per Second - 每秒傳輸幀數)判定的,一般主流的瀏覽器螢幕重新整理率都在 60Hz(每秒重新整理60次),最優的影格率在 60 FPS,影格率越高,頁面就越流暢,60Hz意味著每隔16.6ms會重新整理一次顯示屏,也就是每一次渲染頁面需要在16.6ms內完成,否則就會導致頁面失幀,出現卡頓現象。根因在於:瀏覽器中的 JavaScript 執行和頁面渲染會相互阻塞

在 Chrome 的 devtools 中我們可以執行 Cmd+Shift+P 輸入 show fps 來快速開啟 fps 面板,如下圖所示:

1.png

通過觀察 FPS 面板,我們可以很方便的對當前頁面的流暢度進行監控

2.png

1 影響頁面效能的因素

頁面互動是否流暢,在於頁面響應是否流暢,而頁面響應其本質上就是把頁面狀態的變更重新渲染到頁面上的過程。

頁面響應過程大致如下:

3.png

一般情況Event Handler事件處理邏輯不會消耗太多時間,所以影響angular效能的因素主要在於非同步事件觸發變更檢測。 一般情況Event Handler事件處理邏輯不會消耗太多時間,所以影響angular效能的因素主要在於非同步事件觸發和變更檢測。

對angular來說,頁面渲染的過程就是變更檢測的過程,可以理解為angular的變更檢測要在16.6ms內完成才不會導致頁面失幀、卡頓。

可以從以下三方面優化頁面響應的效能。

(1)對於觸發事件階段,可以減少非同步事件的觸發,來減少整體的變更檢測次數和重新渲染;

(2)對於 Event Handler 執行邏輯階段,可以通過優化複雜程式碼邏輯來減少執行時間;

(3)對於 Change Detection 檢測資料繫結並更新 DOM 階段,可以減少變更檢測模板資料的計算次數來減少渲染時間;

對於(2)Event Handler要具體問題具體分析,不做討論,主要針對(1)(3)進行優化

2 優化方案

2.1 減少非同步事件觸發

Angular在預設變更檢測模式下,非同步事件會觸發全域性的變更檢測,因此,減少非同步事件的觸發會大大的提升angular的效能。

非同步事件包括Macro Task(宏任務)事件和Micro Task微任務事件

4.png

對非同步事件的優化主要是針對document的監聽事件。比如document上的click、mouseup、mousemove…等監聽事件。

監聽事件場景:

Renderer2.listen(document, …)

fromEvent(document,…)

document.addEventListener(…)

dom監聽事件,在不需要觸發的時候一定要移除。

舉例:[pop]提示框指令

使用場景:表格列篩選,點選圖示以外的地方,或者頁面捲動,列篩選彈框隱藏

以前的做法是直接在pop指令裡監聽document的click事件和scroll事件,這樣有個弊端就是提示框未顯示,但依然存在監聽事件,很不合理。

合理的解決方案:當提示框顯示的時候才去監聽click和scroll事件,隱藏的時候就移除監聽事件。

5.png

對於頻繁觸發的dom監聽事件,可以使用rjx的操作符對事件進行優化。詳情參考Rjx操作符RxJS Marbles

2.2 變更檢測

什麼是變更檢測?

要理解變更檢測,我們可以從變更檢測的目標尋找答案。angular變更檢測目標,是讓模型(TypeScript程式碼)與模板(HTML)保持同步。因此,變更檢測可以理解為:檢測模型變更的同時,更新模板( DOM

變更檢測流程是什麼?

6.png

通過在元件樹中按照自頂向下的順序執行變更檢測,也就是先對父元件執行變更檢測,再對子元件進行變更檢測。首先檢查父元件的資料變更,然後更新父元件模板,在更新模板的時候遇到子元件,會去更新子元件上繫結的值,然後進入子元件,看@Input輸入值的索引是否改變,如果改變就將該子元件標記為dirty,也就是後續需要變更檢測的,標記完子元件之後,繼續更新父元件中子元件後面的模板,父元件模板全部更新完之後再去對子元件做變更檢測。

2.2.1 angular變更檢測原理

在預設變更檢測default模式下,非同步事件觸發Angular的變更檢測的原理是 angular通過使用Zone.js處理非同步事件時呼叫了ApplicationRef 的tick()方法從根元件到子元件執行變更檢測。 Angular 應用初始化過程中,範例化了一個zone (NgZone),然後將所有邏輯都跑在該物件的 _inner 物件中。

Zone.js實現了以下幾個類:

  • Zone類,JavaScript 事件的執行環境,和執行緒一樣,它們可以帶一些資料,並且可能擁有父子 zone。
  • ZoneTask類,包裝後的非同步事件,這些 task 有三種子類:
    • MicroTask,由 Promise 建立。
    • MacroTask,由 setTimeout 等建立。
    • EventTask,由 addEventListener 等建立,比如dom事件。
  • ZoneSpec物件,建立一個 ngZone 時給它提供的引數,有三個可以觸發檢測的勾點:
    • onInvoke,呼叫某個回撥函數時觸發的勾點。
    • onInvokeTask,ZoneTask 被觸發時觸發的勾點,比如 setTimeout 到時。
    • onHasTask,檢測到有或無 ZoneTask 時觸發的勾點(即對第一個 schedule 的 zone 和最後一個 invoke 或 cancel 的 task 觸發)。
  • ZoneDelegate類,負責呼叫勾點。

檢測過程原理大概如下:

使用者操作觸發非同步事件(比如:dom事件,介面請求…)

=> ZoneTask類處理事件。invokeTask()函數中呼叫zone的runTask()方法,在runTask方法中,zone通過_zoneDelegate範例屬性,呼叫ZoneSpec的勾點

=> ZoneSpec的三個勾點(onInvokeTask、onInvoke、onHasTask)勾點裡通過checkStable()函數觸發zone.onMicrotaskEmpty.emit(null)通知

=> 根元件監聽onMicrotaskEmpty後呼叫tick(),tick方法中呼叫 detectChanges()從根元件開始檢測

=> ··· refreshView()呼叫executeTemplate()executeTemplate方法中呼叫templateFn()更新模板、子元件繫結的值(這時候會去檢測子元件的@Input()輸入參照是否改變,如果有改變,會將子元件標記為Dirty,也就是該子元件需要變更檢測

詳細變更檢測原理流程圖:

7.png

簡化流程:

觸發非同步事件

=> ZoneTask處理事件

=> ZoneDelegate 呼叫ZoneSpec的勾點觸發onMicrotaskEmpty通知

=> 根元件收到onMicrotaskEmpty通知,執行tick(),開始檢測並更新dom

8.png

由以上程式碼可知,當微任務為空的時候才會觸發變更檢測

簡略變更檢測原理流程圖:

9.png

Angular 原始碼解析 Zone.js參考blog

2.2.2 變更檢測優化方案

1 )使用OnPush 模式

原理:減少1次變更檢測的耗時。

OnPush模式與Default模式的區別在於:dom監聽事件、timer事件、promise都不會觸發變更檢測。Default模式的元件狀態始終為CheckAlways,表示元件每次檢測週期都要檢測。

OnPush模式下:以下情況會觸發變更檢測

S1、元件的@Input參照發生變化。

S2、元件的DOM繫結的事件,包括它子元件的DOM繫結的事件,比如 click、submit、mouse down。@HostListener()

注意:

通過renderer2.listen()監聽的dom事件不會觸發變更檢測

通過dom.addEventListener()原生監聽方式也不會觸發變更檢測

S3、Observable 訂閱事件,同時設定 Async pipe。

S4、利用以下方式手動觸發變化檢測:

ChangeDetectorRef.detectChanges():觸發當前元件和非OnPush子元件的變更檢測。

ChangeDetectorRef.markForCheck():將當前檢視及其所有的祖先標記為髒,下次檢測週期時候會觸發檢測。

ApplicationRef.tick():不會觸發變更檢測

2 )使用NgZone.runOutsideAngular()

原理:減少變更檢測次數

將全域性dom事件監聽寫在NgZone.runOutsideAngular()方法的回撥裡面,dom事件將不會觸發angular的變更檢測。如果當前元件未更新,可以在回撥函數裡執行ChangeDetectorRef的detectChanges()勾點來手動觸發當前元件的變更檢測。

舉例:app-icon-react動態圖示元件

10.png

2.2.3 偵錯方式

方式1:可以在瀏覽器控制檯,使用Angular DevTools外掛檢視某一次dom事件,angular的檢測情況:

11.png

方式2:可以在控制檯直接輸入:ng.profiler.timeChangeDetection()檢視檢測時間,這種方式可檢視全域性的檢測時間。參考部落格 Profiling Angular Change Detection

12.png

2.3 模板(HTML)優化

2.3.1 減少DOM渲染:ngFor加trackBy

使用 *ngFor 的 trackBy 屬性,Angular 只更改和重新渲染已更改的條目,而不必重新載入整個條目列表。

比如:表格排序場景。ngFor如果加了trackBy,表格渲染的時候只會移動行dom元素,如果不加trackBy,會先刪除現有的表格dom元素,再新增行dom元素。顯然只移動dom元素效能會好很多。

2.3.2 模板表示式中不要使用函數

不要在Angular 模板表示式中使用函數呼叫,可以用管道pipe代替,也可以通過手動計算後用一個變數代替。模板中使用函數,不管值有沒有改變,每次變更檢測的時候都會執行函數,會影響效能。

模板中使用函數的場景:

13.png

2.3.3 減少ngFor的使用

使用ngFor,資料量大的時候會影響效能。

舉例:

使用ngFor:

14.png

15.png

不使用ngFor:效能提升10倍左右

16.png

17.png

更多程式設計相關知識,請存取:!!

以上就是angular如何進行效能優化?變更檢測方式淺析的詳細內容,更多請關注TW511.COM其它相關文章!