1 Optimization Direction
-
Start loading performance
-
Rendering performance
2 Enable the loading performance
2.1 Mechanism of Boot Loading
The startup of applets is mainly divided into logical layer and view layer. The logic layer performs JS code logic, and the view layer takes WebView as the carrier to complete rendering and updating of page content.
Before starting a small program, the client preloads the basic environment of the small program to improve the loading speed of the small program. When the user opens the small program, the code package will be downloaded first. After downloading, the business code of the executive developer will be injected into the logic layer and the view layer respectively. Finally, the execution results will be aggregated and rendered to the first screen content.
That is, there are three stages in the startup process of small programs: resource preparation (code package download); Injection of business code and first rendering of landing page; Loading state of a landing page request (this is not done when the landing page does not request any data).
For example:
Figure 1: Download the applets. Small program mainly in resource preparation, mainly code package download and verification work.
Figure 2: Loading the applet code package. The applet is injecting and executing business code, waiting for the first rendering to complete.
Figure 3: Initialize the applet home page. When the applet finishes rendering for the first time, for many applet it does not mean that the page content appears completely, but needs to communicate with the server once to get the data to render. Figure 3 shows the process of waiting for a request to return in such a scenario.
2.2 Enabling performance optimization
2.2.1 Code package volume optimization: Control the size of code package
In the process of applet loading, the size of code package has the most direct effect on the performance of the whole applet loading startup.
Therefore, the most direct and effective way to improve the startup and loading performance of small programs is to reduce the size of the code package.
The code package size can be reduced by:
-
Turn on the “Code Compression” option in developer tools.
-
Subcontract loading.
-
Immediate cleanup of obsolete code, especially large third-party libraries, and unused resource files such as images.
-
Control resources such as images in code package: reduce resource files such as local images and use network image codes if necessary.
2.2.2 Developer code injection optimization
-
Reduce synchronous calls to the startup process
-
Enable lazy injection (unused code files will not be executed after adding this configuration)
{ “lazyCodeLoading”: “requiredComponents” }
2.2.3 Page rendering optimization
- Request in advance
- Data requests do not depend on the integrity of the page structure, and can be initiated during page onload or code injection without waiting for page rendering to complete. The time the user waits for the request to return is further reduced.
- Data prepull
- Periodic renewal
- Using the cache
Use storage API to request results into the cache, secondary startup, directly with the cache data to complete rendering, and then in the background according to update, to ensure that users see the page content for the first time, at the same time, even in no network environment, users can also use part of the function of the small program.
- Avoid bad
During the request process, a basic skeleton is displayed in the page first and the existing data is displayed, so that users can have a psychological expectation of the page content and reduce the possibility of leaving while waiting.
- Simplify the first screen data
- Timely feedback
For some time-consuming operations, in the process of waiting for users, feedback of interactive operations should be given to avoid users thinking that the small program has no response.
-
Enable Initial Render Cache
2.3 Appendix: Subcontract loading
2.3.1 Subcontract loading mechanism
According to the business scenario, the pages with high user visiting rate are placed in the main package, and the pages with low user visiting rate are placed in the sub-package and loaded on demand.
When the small program starts, the main package will be downloaded and the page in the main package will be started by default. When the user enters a page in the subpackage, the client will download the corresponding subpackage and display it after downloading.
2.3.2 Independent subcontracting
Independent subcontracting is a special type of subcontracting in small programs that can operate independently of the main package and other subcontracting. There is no need to download the main package when entering the applet from the standalone subcontracting page. The main package is downloaded only when the user enters the normal package or main package page.
Some pages with some functional independence can be configured into independent subcontracting as needed. When the applets start from the normal subcontracting page, the main package needs to be downloaded first; Independent subcontracting can run independently of the main package, which can greatly improve the startup speed of the subcontracting page.
2.3.3 Subcontract pre-download
Developers can configure the framework to automatically download subcontracts that may be needed when entering a certain page of the small program, so as to improve the startup speed when entering subsequent subcontracting pages. For standalone subcontracting, the main package can also be pre-downloaded.
3 rendering performance optimization
3.1 Principle of small program rendering
Applets are based on a two-threaded model.
In this architecture, the rendering layer of the applet uses WebView as the rendering carrier, while the logic layer runs JS scripts by an independent JsCore thread. The two sides do not have a direct channel to share data, so the communication between the rendering layer and the logic layer is mediated by the Native JSBrigde.
Communication flow of applets updating view data:
Whenever the applet view data needs to be updated, the logical layer will call the setData method provided by the applet host environment to pass the data from the logical layer to the view layer, and complete the UI view update after a series of rendering steps. The complete communication process is as follows:
-
The applet logic layer calls the setData methods of the host environment.
-
The logic layer performs json.stringify to convert the data to be transferred into a string and concatenate it into a specific JS script, and the script executes evaluateJavascript to transfer the data to the rendering layer.
-
After the rendering layer receives it, the WebView JS thread will compile the script, get the data to be updated, enter the rendering queue and wait for the WebView thread to render the page when it is idle.
-
When the WebView thread starts rendering, the data to be updated is merged into the original data retained by the view layer and applied to the WXML fragment to create a new virtual tree of nodes. After diff comparison of the new virtual node tree with the current node tree, the differences are updated to the UI view. At the same time, the new node tree replaces the old node tree for the next re-rendering.
To sum up, the logical layer transmits data to the view layer using setData, and the view layer updates the page. From the perspective of architecture, the logical layer and the view layer cannot share data directly. Data transmission is a cross-process communication, and there is a certain communication cost, which is positively correlated with the amount of data transmitted.
3.2 Use setData correctly
- Avoid placing data in data that is not related to rendering, and only place data in data that is related to page rendering.
- Avoid using setData to transfer a large amount of data at once and only use setData for data that has changed. When the amount of data reaches 1MB, the time can increase to hundreds of milliseconds, which can take 1s or more on some low-end models.
For example, for long lists, use setData for list partial refresh.
Const list = requestSync(); // Update the list this.setData({list});Copy the code
In fact, if only individual fields need to be updated, we can write this to avoid updating the entire list:
Const list = requestSync(); / / update part list enclosing setData ({' list [0]. SRC ': the list [0]. SRC});Copy the code
Or:
Let feedList = [[array]]; Page ++ let page = 1 // 3 Every time I scroll to the bottom of the page, OnReachBottom :()=>{fetchNewData(). Then ((newVal)=>{this.setData({['feedList[' + (page-1) + ']']: newVal, }) } } // 4. We end up with [[array1],[array2]], and then wx:for traverses the rendered dataCopy the code
-
Do not call setData frequently in a short period of time, and merge the consecutive setData as much as possible. Frequent calls will result in the WebView processing data and updating pages for a period of time, and the WebView cannot respond to user operations, resulting in operation delay. In addition, the WebView cannot timely feed back user operation events to the logical layer, resulting in interaction delay.
-
Do not setData in background pages
Although each page of the small program is run in an independent Webview, the JS engine of webview is shared. If a page misuses setData, it will seize resources and affect the operation of other pages.
For example: there is a countdown in the page, such as the seckill activity of e-commerce. When the seckill page enters the background, if there is no customized timer, the background page will be constantly updated and occupy the current resources.
3.3 Correct use of events
When the view layer feeds events back to the logic layer, there is also a communication process that goes from the view layer to the logic layer. Because this communication process is asynchronous, there will be a certain delay. The delay time is also positively correlated with the amount of data transferred, and it is within 30ms when the amount of data is less than 64KB.
Methods to reduce delay:
-
Remove unnecessary event bindings (bind and catch in WXML) to reduce the amount and frequency of communication;
-
The dataset of Target and currentTarget needs to be transferred during event binding, so do not place too much data in the data prefix property of the node.
3.4 Avoid improper use of page slide (onPageSrcoll) listener callback
Each event listener is a view-to-logic communication process, so pageSrcoll is listened on only when necessary.
- Ship onpage Rcoll event only when necessary.
- Avoid executing complex logic in onPageSrcoll.
- Avoid frequent calls to setData in onPageSrcoll.
- In certain scenarios, such as exposure counts. The usual practice is to listen to the onPageSrcoll event and constantly query the location of the element. Create electorQuery is also very time-consuming and can affect the performance of the applet communication rendering. In this case, IntersectionObserver (IntersectionObserver) is used instead to reduce unwanted communications.
When setData needs to be called in frequently triggered user events (such as PageScroll and Resize events), proper use of debounce and throttle functions can reduce the number of setData executions.
Function debounce: the function is executed only once after n seconds. If the function is repeatedly triggered within n seconds, the time is recalculated. Function throttling: Functions are throttled once per unit of time. If functions are throttled multiple times per unit of time, functions take effect only once.
We can also design a diff algorithm to re-encapsulate setData, so that before the execution of setData, the data to be updated can be diff compared with the original data, and the patch object of data difference can be calculated to determine whether the patch object is empty. If it is empty, the update will be skipped; otherwise, setData operation will be performed on the patch object, so as to reduce the amount of data transmission and the frequency of setData execution.
// setData is repackaged into a new method that diff compares the old data with the new data before updating it. Update = (data) => {return new Promise((resolve, reject) => {const result = diff(data, this.data); if (! Object.keys(result).length) { resolve(null); return; } this.setData(result, () => { resolve(result); }); }); }Copy the code
The specific process is as follows:
3.5 Using Custom Components
In addition to facilitating code reuse and improving development efficiency, custom components can also effectively improve the performance of frequently updated pages.
Updates to custom components occur only within the component and are not affected by the rest of the page, greatly reducing the overhead of page updates.
The implementation of applets custom components is supported by the Exparser framework designed by applets.
When initializing the page, Exparser not only creates the page instance, but also instantiates the component based on the registration information of the user-defined component. Then, it constructs an independent Shadow Tree based on the data and WXML of the component. Append to the page Composed Tree. The created Shadow Tree has its own independent logical space, data, style environment, and setData calls:
Based on the Shadow DOM model design of custom components, we can encapsulate some function modules (such as countdown, progress bar, etc.) that need to perform setData update frequently in the page into custom components and embed them in the page.
When these custom component views need to be updated, the component’s own setData is performed, the comparison of the old and new node trees and the update of the render tree are limited to a limited number of nodes within the component, effectively reducing the rendering time overhead.
Of course, it is not that the more custom components are used, the better. Each new custom component is added to the page, Exparser needs to manage one more component instance, which will consume more memory. When the memory usage rises to a certain extent, iOS may reclaim part of WKWebView, and Android experience will become more sluggish. Therefore, the use of custom components should be reasonable, and the page design should be careful not to abuse tags.
3.6 Minimize the number of nesting layers of data structures
The time cost is roughly proportional to the total number of nodes in the node tree. Therefore, reducing the number of nodes in WXML can effectively reduce the time cost of initial rendering and re-rendering and improve rendering performance.
During initial rendering, the initial data is applied to the corresponding WXML fragment to generate a node tree. Finally, each component is successively created on the interface according to each node contained in the node tree.
During re-rendering, the comparison between the current node tree and the new node tree will focus on the comparison of node attributes affected by setData data. Therefore, removing unnecessary data and reducing the amount of setData can also help improve the performance of this step.
3.7 The role of key values in list rendering
Key values can improve list rendering performance during list rendering.
Small program page is how to render, mainly divided into the following steps:
-
Build a WXML-structured document into a VDOM dummy number
-
The page has a new interaction, generates a new VDOM number, and then compares it to the old number to see where it has changed, and makes the corresponding modification (delete, move, update values), etc.
-
Finally, render the VDOM into a real page structure
The key value is used in the second step. Components with keys are corrected when data changes trigger the rendering layer to re-render. The framework ensures that they are reordered, not recreated, to keep the components in their own state and to improve the efficiency of the list rendering.
If the key value is not specified, it is treated by the index of the array by default, which can cause confusion in the values of input box components such as input.
-
Append elements to the end of the array without a key. Previously rendered elements are not re-rendered. But if an element is inserted in the header or middle, the entire list is removed and re-rendered, and the values of the input component are scrambled and not updated properly.
-
Add a key, insert an element at the end, middle, or head of the array, and other existing elements will not be re-rendered and their values will be updated normally. Therefore, when rendering a list, it is better to add a key if the order of the list changes, and do not simply use the array index as the key.
PS: Personally experienced data, if the initial rendering of your original small program reaches about 260ms, handle the basic optimization of custom components, setData related optimization and so on. If the long list has not been optimized, the initial rendering can be improved to about 140ms.