In an interview, I was asked about frontend performance optimizations. After a series of answers, the interviewer further asked: What performance optimizations can be done on the frontend?
Frontend optimizations can be approached in several directions:
Network Optimization
Page Rendering Optimization
JS Optimization
Image Optimization
webpack Packaging Optimization
React Optimization
Vue Optimization
Network Optimization
DNS Pre-fetching
Use the rel attribute of the link tag to set dns-prefetch and get the IP address corresponding to the domain name in advance.
Use Cache
educe server pressure and quickly get data (see here for strong cache and negotiated cache).
Use the CDN (Content Distribution Network)
The physical distance between the user and the server also affects the response time.
Compress Responses
Reduce the size of the response package generated by HTTP requests to improve performance by decreasing transmission time.
Use Multiple Domains
Modern browsers such as Chrome will have the same domain name limit and concurrent download number. Using different domain names can maximize download threads, but try to keep within 2~4 domain names to avoid DNS query loss.
Avoid Null Image src
Empty string for src attribute still makes the browser issue an HTTP request to the server.
IE sends a request to the directory where the page is located; Safari, Chrome, and Firefox send a request to the page itself; Opera does not perform any operation.
Page Rendering Optimization
Process of Webkit rendering engine
Handles HTML and builds a DOM tree
Handles CSS and builds a CSS rules tree (CSSOM)
Merges the DOM Tree and CSSOM Tree to form a Render Tree.
Layouts according to the Render Tree and calculates the position of each node
Calls GPU drawing, composites layers, and displays on the screen
Avoid CSS blocking
CSS affects the construction of the Render Tree and may block page rendering. Therefore, you should try to load the CSS resource as early (put the CSS in the head tag) and as fast (use CDN to optimize the loading speed of static resources) as possible.
Reduce the complexity of CSS selectors
Browsers read selectors from right to left.
Reduce nesting: Don’t exceed three levels, and be cautious about using descendant selectors, which have high costs.
Avoid using universal selectors, match only the elements needed
Take advantage of inheritance, avoid repetitive matches and definitions
Use class selectors and id selectors correctly
Avoid using CSS expressions
CSS expressions are calculated frequently.
Avoid JS blocking
JS can modify CSSOM and DOM, so JS can block the parsing and rendering of pages, and it will wait for the loading of CSS resources. In other words, JS will take over control of the rendering engine. Therefore, we need to add defer or async to our JS resources to delay the execution of JS scripts.
Use externally linked JS and CSS
In real environments, using external files generally leads to faster pages because JavaScript and CSS have a chance to be cached by the browser. For inline situations, HTML documents usually aren’t configured to be cacheable, so for every HTML document request, JavaScript and CSS need to be downloaded. Therefore, if JavaScript and CSS are in external files, the browser can cache them, the size of HTML documents will be reduced without increasing the number of HTTP requests.
Use font icons (Iconfont) instead of image icons
Images increase the number of network requests, thereby slowing down page load time.
Iconfont can scale well and won’t add extra requests.
First Screen Load Optimization
Use a skeleton screen or animation to optimize user experience
Load resources on demand; delay loading of resources not needed on the homepage
Reduce Repaint and Reflow
Add multiple nodes using documentFragment: This is not part of the actual DOM, and will not cause repaint and reflow
Use translate instead of top because top will trigger reflow, but translate will not. So translate saves one layout time compared to top
Replace display: none with visibility, because the former will only cause repaint, while the latter will trigger reflow (changing the layout); replace visibility with opacity, visibility will trigger repaint (paint), but opacity won’t.
Modify the DOM offline, for example: First set the DOM to display:none (causing one reflow), then you modify it 100 times, and then display it again
Don’t put the property values of DOM nodes in a loop as variables of the loop.
For example in Javascript:
|
|
Try to use table layout less, because every time a cell’s layout changes, the entire table will go through reflow and repaint.
Don’t frequently manipulate DOM nodes. It’s better to write the styles that need to be manipulated into classes in advance, and then modify them when needed. A single reflow and repaint is much less costly than multiple reflows and repaints.
Choose the speed of animation implementation. The faster the animation speed, the more reflows, you can also choose to use requestAnimationFrame.
Every time you access the offset attributes of the DOM, such as getting an element’s scrollTop, scrollLeft, scrollWidth, offsetTop, offsetLeft, offsetWidth, offsetHeight, etc., the browser will reflow to get the latest value in order to ensure the correctness of the value. Therefore, if you need to operate multiple times, take a cache after the extraction. And don’t access DOM offset attributes in a for loop. When used, it is best to define a variable and assign the needed value to it, cache the value, and reduce the number of reflows and repaints.
Convert frequently running animations to layers. Layers can prevent this node’s reflow from affecting other elements. For example, for the video tag, the browser will automatically turn this node into a layer.
Performance Optimization in JS
Use event delegation
Implement debounce and throttle
Try not to use JS animations.
CSS3 animations and canvas animations perform better than JS animations.
Multithreading
Start a webWorker for complex calculations to avoid page freezing.
Cache Calculation Results
Reduce the number of calculations, such as using computed in Vue.
Image Optimization
Using Sprites
Optimize by reducing the number of HTTP requests.
Lazy Loading Images
Load the image as it is about to enter the viewport (For how to judge whether the image enters the viewport, please refer to here)
Use CSS3 Instead of Images
There are many images that can be drawn with CSS effects (gradients, shadows, etc.). In this case, choosing CSS3 effects is better.
Image Compression
There are two ways to compress images. One is to compress them through an online website. The other is to use the webpack plugin image-webpack-loader, which is based on the Node library imagemin for image compression.
Use Progressive JPEG
Using progressive JPEG will improve the user experience.
Use WebP Format Images
WebP is a new image file format that provides both lossy and lossless compression. With the same image quality, the volume of WebP is smaller than PNG and JPG.
Webpack Packaging Optimization
Narrow Down the Range of Loader Matches
Optimize loader configuration
Use the three configuration items test, include, exclude to narrow down the processing range of the loader.
It’s recommended to use include.
|
|
Resolve.Modules
resolve.modules is used to configure which directories webpack goes to look for third-party modules, by default it’s node_modules
.
By default, it looks for third-party modules in the node_modules
directory under the current project directory. If it doesn’t find them, it will go to the ../node_modules
directory above. If it doesn’t find them there, it will go to the ../../node_modules
directory, and so on. This is similar to Node.js module search mechanism.
If we install all third-party modules in the root directory of the project, we can specify this path directly.
|
|
Resolve.Extensions
If the import statement does not have a file suffix, webpack will automatically add the suffix and then try to find out whether the file exists or not.
Keep the list of suffixes as small as possible
Try to put a suffix in the import statement as much as possible.
If you want to optimize to the extreme, it’s not recommended to use extensionx, because it will consume some performance, even though it can bring some convenience.
Extract CSS
With the help of mini-css-extract-plugin: this plugin extracts CSS to a separate file, creates a CSS file for each JS file containing CSS, and supports on-demand loading of CSS and SourceMaps.
|
|
Code Compression
- JS Code Compression
With mode:production, terser-webpack-plugin is used.
|
|
- CSS Code Compression
Using css-minimizer-webpack-plugin
|
|
- HTML File Code Compression
|
|
When you set minify, it will actually use another plugin html-minifier-terser.
- File Size Compression
Compress the size of the file to reduce the bandwidth loss during HTTP transmission.
|
|
- Image Compression
Normally, after packaging, the size of some image files is far larger than that of js or css files, so image compression is quite important.
The configuration method is as follows:
|
|
Tree Shaking to Remove Dead Code
Tree Shaking is a term in computing that means eliminating dead code, or code that is no longer being used. It relies on the static syntax analysis of ES Module (without executing any code, we can clearly understand the dependency relationship of the module)
Webpack provides two different approaches to implementing Tree Shaking:
usedExports: By marking whether certain functions are used and then optimizing them through Terser
sideEffects: It skips the entire module/file and directly checks if the file has side effects
Different configuration plans have different effects.
usedExports
The configuration method is very simple, you just need to set usedExports to true.
|
|
After using, the unused code in webpack packaging will have an “unused harmony export mul” comment added to notify Terser that this section of code can be deleted when optimizing.
sideEffects
sideEffects is used to inform the webpack compiler which modules have side effects. The configuration method is to set the sideEffects attribute in the package.json.
If sideEffects is set to false, it informs webpack that it can safely delete unused exports.
If some files need to be retained, it can be set in an array form.
|
|
The above are all about the tree shaking of javascript. CSS can also achieve tree shaking.
css tree shaking
You can install the PurgeCss plugin for CSS optimization.
|
|
paths: Represents which directories need to be analyzed. It is used in conjunction with glob.
By default, Purgecss will remove the style of our HTML tags. If we want to keep it, we can add a safelist attribute.
Code Splitting
This is the process of dividing code into different bundles, which can then be loaded on demand or in parallel.
By default, all the JavaScript code (business code, third-party dependencies, currently unused modules) is loaded on the homepage, which may impact the page load speed.
Code splitting can break the code into smaller bundles and control the load priority of resources to improve the performance of code loading.
This can be achieved by using the SplitChunksPlugin, which is already integrated in webpack by default, and only needs configuration.
|
|
The main attributes of splitChunks are as follows:
Chunks: This handles synchronous or asynchronous code.
minSize: The size of the split package. It must be at least minSize. If the package size does not exceed minSize, this package will not be split.
maxSize: Packages larger than maxSize will be split into packages that are not smaller than minSize.
minChunks: The number of times it is quoted, the default is 1.
Multi-threaded Packaging to Improve Packaging Speed.
Vue
Add a key in v-for
Lazy load routes
Use third-party plugins on demand
Make reasonable use of computed and watch
Avoid using v-if while using v-for
Destroy events when destroy, such as events added by addEventListener, setTimeout, setInterval, etc.
When rendering large lists, optimize by using virtual lists.
Use Keep-Alive to cache components and avoid repeated rendering of components.
Use Teleport to render the component’s DOM into a designated container, thus avoiding unnecessary DOM operations.
Use Suspense to display a placeholder while the component is not yet loaded, thus avoiding repeated rendering of components.
Use Fragments to avoid unnecessary DOM operations.
Use Memoization to avoid unnecessary calculations.
React
Add a key when looping with map
Lazy load routes
Use third-party plugins on demand
Use scu, memo or PureComponent to avoid unnecessary rendering
Make reasonable use of useMemo, memo, useCallback
These three are all applied in caching results to avoid unnecessary calculations or rendering when the dependent value has not changed.
useCallback is used to “remember” a function. When its dependent item does not change, the reference of the function will not be reassigned with the refresh of the component. We can use useCallback to modify it when we think a function does not need to update its reference address with the update of the component.
React.memo is used to “remember” a component. When it receives props that have not changed, it will return the result of the last rendering, and will not re-execute the function to return a new rendering result.
React.useMemo is a kind of “memory” for value calculation. When the dependent item does not change, there is no need to calculate it again, just use the previous value. For the component, one of the benefits is that it can reduce some calculations and avoid some unnecessary rendering. When we encounter some data that needs to be calculated within the component, we can consider React.useMemo.