Regarding the description of front-end performance indicators, the industry has its own opinions. In summary, it is related to the first screen performance and page fluency. This time, we will optimize the page interaction performance from the perspective of page fluency. analyze. [Related tutorial recommendation: "Angular Tutorial"]
What is page fluency?
Page fluency is determined by the frame rate FPS (Frames Per Second - frames transmitted per second). Generally, the screen refresh rate of mainstream browsers is 60Hz (refreshed 60 times per second), and the optimal frame rate is 60 FPS. The higher the frame rate, the smoother the page. 60Hz means that the display screen will be refreshed every 16.6ms, which means that each page rendering needs to be completed within 16.6ms, otherwise the page will lose frames and freeze.根因在于:浏览器中的JavaScript 执行和页面渲染会相互阻塞
.
In Chrome's devtools, we can execute Cmd+Shift+P and enter show fps to quickly open the fps panel, as shown in the following figure:
By observing the FPS panel, we can easily monitor the fluency of the current page.
1 Factors affecting page performance
Whether the page interaction is smooth depends on whether the page response is smooth, and the page response is essentially the process of re-rendering changes in the page status to the page.
The page response process is roughly as follows:
Generally, the Event Handler event processing logic does not consume too much time, so the factors that affect Angular performance mainly lie in异步事件触发
and变更检测
. Generally, the Event Handler event processing logic does not consume too much time, so the factors that affect Angular performance mainly lie in asynchronous event triggering and change detection.
For Angular, the process of page rendering is the process of change detection. It can be understood that Angular's change detection must be completed within 16.6ms to avoid page frame loss and lag.
The performance of page response can be optimized from the following three aspects.
(1) For the trigger event stage, the triggering of asynchronous events can be reduced to reduce the overall number of change detections and re-rendering;
(2) For the Event Handler execution logic stage, the execution time can be reduced by optimizing complex code logic;
(3) For the Change Detection detection data binding and DOM update phase, the number of calculations of change detection and template data can be reduced to reduce rendering time;
for (2) Event Handler, specific issues need to be analyzed in detail and will not be discussed. Mainly focus on (1) (3) ) Optimization
2 Optimization Plan
2.1 Reduce asynchronous event triggering
Angular In the default change detection mode, asynchronous events will trigger global change detection. Therefore, reducing the triggering of asynchronous events will greatly improve the performance of Angular.
Asynchronous events include Macro Task events and Micro Task events
The optimization of asynchronous events is mainly for document listening events. For example, listen for click, mouseup, mousemove... and other events on the document.
Listening event scenario:
Renderer2.listen(document,…)
fromEvent(document,…)
document.addEventListener(…)
DOM listening event must be removed when it is not needed to be triggered.
For example: [pop] prompt box command
usage scenarios: filtering table columns, clicking somewhere other than the icon, or scrolling the page, hiding the column filter pop-up box.
The previous method was to directly monitor the click event and scroll event of the document in the pop command. This way: The only drawback is that the prompt box is not displayed, but there are still monitoring events, which is very unreasonable.
Reasonable solution: Only listen to click and scroll events when the prompt box is displayed, and remove the listening events when it is hidden.
For frequently triggered DOM listening events, you can use rjx operators to optimize events. See Rjx operators for details. RxJS Marbles.
2.2 Change Detection
What is change detection?
To understand change detection, we can look for answers from the goals of change detection. The goal of angular change detection is to keep the model (TypeScript code) and the template (HTML) in sync. Therefore, change detection can be understood as: detecting model changes and updating the template ( DOM ) at the same time .
What is the change detection process?
By performing change detection in a top-down order in the component tree, that is, change detection is performed on the parent component first, and then on the child components. First check the data changes of the parent component, and then update the parent component template. When updating the template, when encountering the child component, it will update the value bound to the child component, and then enter the child component to see if the index of the @Input input value has changed. If it changes, mark the subcomponent as dirty, which requires subsequent change detection. After marking the subcomponent, continue to update the template behind the subcomponent in the parent component. After all the parent component templates have been updated, make changes to the subcomponent. detection.
2.2.1 Principle of angular change detection
In the default change detection default mode, the principle of asynchronous events triggering Angular's change detection is that angular calls the tick() method of ApplicationRef when processing asynchronous events using Zone.js to execute from the root component to the sub-component. Change detection. During the initialization process of the Angular application, a zone (NgZone) is instantiated, and then all logic is run in the _inner object of the object.
Zone.js implements the following classes:
The principle of the detection process is roughly as follows:
user operations trigger asynchronous events (for example: DOM events, interface requests...)
=> ZoneTask class handles events. The runTask() method of zone is called in the invokeTask() function. In the runTask method, zone passes the _zoneDelegate instance attribute and calls the hook of ZoneSpec
=> the three hooks of ZoneSpec (onInvokeTask, onInvoke, onHasTask) pass the checkStable() function Trigger zone.onMicrotaskEmpty.emit(null) notification
=> The root component calls tick() after listening to onMicrotaskEmpty, and calls detectChanges()
in the tick method to start detection from the root component
=> ··· refreshView()
calls executeTemplate()
, in the executeTemplate
method Call templateFn()
to update the value bound to the template and subcomponent (这时候会去检测子组件的
输入引用是否改变,如果有改变,会将子组件标记为
Dirty ,也就是该子组件需要变更检测
)
Detailed change detection principle flow chart:
Simplify the process:
trigger an asynchronous event
=> ZoneTask handles the event
=> ZoneDelegate calls the ZoneSpec hook to trigger the onMicrotaskEmpty notification
=> the root component receives the onMicrotaskEmpty notification, executes tick(), and starts detecting and updating the dom
As can be seen from the above code,当微任务为空的时候才会触发变更检测
.
Simple change detection principle flow chart:
Angular source code analysis Zone.js reference blog.
2.2.2 Change detection optimization solution
1) Use OnPush mode
principle: Reduce the time-consuming of one change detection.
The difference between OnPush mode and Default mode is that DOM listening events, timer events, and promises will not trigger change detection. The component status in Default mode is always CheckAlways, which means that the component must be tested every detection cycle.
In OnPush mode: The following situations will trigger change detection
S1 and the @Input reference of the component to change.
S2. Events bound to the component's DOM, including events bound to the DOM of its subcomponents, such as click, submit, and mouse down. @HostListener()
Note:
DOM events monitored through renderer2.listen() will not trigger change detection.
The native listening method of dom.addEventListener() will not trigger change detection.
S3 and Observable subscription events are also set. Async pipe is set at the same time.
S4. Use the following methods to manually trigger change detection:
ChangeDetectorRef.detectChanges(): Trigger change detection for the current component and non-OnPush subcomponents.
ChangeDetectorRef.markForCheck(): Mark the current view and all its ancestors as dirty, and the detection will be triggered in the next detection cycle.
ApplicationRef.tick(): Will not trigger change detection
2)
Principle of using NgZone.runOutsideAngular(): Reduce the number of change detections
and write global dom event monitoring in the callback of the NgZone.runOutsideAngular() method. The dom event will not trigger angular. Change detection. If the current component has not been updated, you can execute the detectChanges() hook of ChangeDetectorRef in the callback function to manually trigger the change detection of the current component.
Example: app-icon-react dynamic icon component
2.2.3 Debugging method
1: You can use the Angular DevTools plug-in in the browser console to view a certain DOM event and the detection status of angular:
Method 2: You can directly enter: ng.profiler.timeChangeDetection() in the console to view the detection time. This method can view the global detection time. Reference blog Profiling Angular Change Detection
2.3 Template (HTML) Optimization
2.3.1 Reduce DOM rendering: ngFor plus trackBy
Using the trackBy attribute of *ngFor, Angular only changes and re-renders the changed entries without having to reload the entire entry list.
For example: table sorting scenario. If trackBy is added to ngFor, only the row dom elements will be moved when the table is rendered. If trackBy is not added, the existing table dom elements will be deleted first, and then the row dom elements will be added. Obviously the performance of moving only dom elements will be much better.
2.3.2 Do not use functions in template expressions.
Do not use function calls in Angular template expressions. You can use a pipe instead, or you can use a variable after manual calculation. When functions are used in templates, regardless of whether the value has changed or not, the function will be executed every time change detection is performed, which will affect performance.
Scenarios for using functions in templates:
2.3.3 Reduce the use of ngFor.
Using ngFor will affect performance when the amount of data is large.
Example:
Using ngFor:
Not using ngFor: performance improved by about 10 times