I have always heard that Dom is slow and should be operated as little as possible. So I want to further explore why everyone will say this. I learned some materials online and sorted them out here.
First of all, the DOM object itself is also a JS object, so strictly speaking, it is not slow to operate on this object, but that some browser behaviors will be triggered after the object is operated, such as layout) and painting ). Next, we will introduce these browser behaviors, illustrate how a page is finally presented, and explain some bad practices and some optimization solutions from the code perspective.
How does a Browser display a page?
A browser has many modules, including the rendering engine module for page presentation, WebKit and gecko, which only involve the content of this module.
First, let's give a rough description of this process:
- Parse HTML and generate a DOM tree
- Parse various styles and combine DOM tree to generate a render tree
- Calculates layout information for each node of the render tree, such as the position and size of the box.
- Draw Based on the render tree and the UI Layer of the browser
The nodes on the DOM tree and render tree do not correspond one to one, for example,"display:none"
The node will only exist on the DOM tree and will not appear on the render tree, because this node does not need to be drawn.
It is the basic process of WebKit. It may be different from gecko in terms of terms. Here, the gecko flowchart is attached. However, the content below will use the terminology of WebKit in a unified manner.
There are many factors that affect page rendering. For example, the link position may affect the presentation of the first screen. However, here we will focus on Layout-related content.
Painting is a time-consuming process. However, layout is a more time-consuming process. We cannot determine whether layout must be implemented from top to bottom or from the bottom up, even a layout operation involves re-calculation of the entire document layout.
However, layout cannot be avoided, so we need to minimize the number of layout times.
When will the browser perform layout?
Before considering how to minimize the number of layout operations, you need to know when the browser will perform layout.
Layout (reflow) is generally called layout. This operation is used to calculate the position and size of elements in the document and is an important step before rendering. When HTML is loaded for the first time, there will be a layout event. js script execution and style changes will also lead to browser execution of layout, which is also the main content to be discussed in this article.
In general, layout is lazy in the browser. That is to say, Dom will not be updated during Javascript script execution, and any modifications to the Dom will be saved in a queue, after the execution context of the current JS is complete, layout is performed based on the modifications in the queue.
However, sometimes the browser has to execute layout in advance to immediately obtain the latest Dom node information in JS Code, which is the main cause of Dom performance problems.
The following operations will break the general rule and trigger the browser to execute layout:
- Get the DOM attribute to be calculated using JS
- Add or delete DOM elements
- Resize browser window size
- Change font
- CSS pseudo-class activation, such as hover
- Modify the DOM element style through JS and the style involves changing the size
Here is an intuitive example:
1 // Read 2 var h1 = element1.clientHeight; 3 4 // Write (invalidates layout) 5 element1.style.height = (h1 * 2) + ‘px‘; 6 7 // Read (triggers layout) 8 var h2 = element2.clientHeight; 9 10 // Write (invalidates layout)11 element2.style.height = (h2 * 2) + ‘px‘;12 13 // Read (triggers layout)14 var h3 = element3.clientHeight;15 16 // Write (invalidates layout)17 element3.style.height = (h3 * 2) + ‘px‘;
Clientheight, this attribute needs to be calculated, so a layout of the browser will be triggered. We can use chrome (v47.0)'s developer tool to check that (timeline record in has been filtered and only layout is displayed ):
In the above example, the code first modifies the style of an element, and then readsclientHeight
Attribute. The current Dom is marked as dirty due to previous modifications. To ensure that this attribute can be accurately obtained, the browser will perform layout (we find that Chrome's developer tool prompts us this performance problem ).
It is easy to optimize this code. Read the required attributes in advance and modify them together.
1 // Read2 var h1 = element1.clientHeight; 3 var h2 = element2.clientHeight; 4 var h3 = element3.clientHeight;5 6 // Write (invalidates layout)7 element1.style.height = (h1 * 2) + ‘px‘; 8 element2.style.height = (h2 * 2) + ‘px‘; 9 element3.style.height = (h3 * 2) + ‘px‘;
Let's take a look at this situation:
Next we will introduce some other optimization solutions.
Minimize Layout
The above mentioned batch read/write is mainly caused by obtaining an attribute value to be calculated. What values need to be calculated?
This link describes most of the properties to be computed: http://gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-webkit.html
Let's take a look at other situations:
A series of Dom operations
For a series of Dom operations (addition, deletion, and modification of DOM elements), the following solutions are available:
- Documentfragment
- Display: None
- Clonenode
For example (only documentfragment is used as an example ):
var fragment = document.createDocumentFragment(); for (var i=0; i < items.length; i++){ var item = document.createElement("li"); item.appendChild(document.createTextNode("Option " + i); fragment.appendChild(item);}list.appendChild(fragment);
The core idea of this type of optimization solution is the same, that is, to first perform a series of operations on a node not on the render tree, and then add the node back to the render tree, in this way, no matter how complex a DOM operation is, layout is triggered only once.
Face style Modification
For style changes, we first need to know that not all style changes will trigger layout, because we know that layout is used to calculate the size and size of renderobject, if I only change the color, layout is not triggered.
Here is a website CSS triggers, which lists in detail the effect of each CSS attribute on layout and painting in the browser.
In the following case, the optimization part is the same. Pay attention to read and write.
elem.style.height = "100px"; // mark invalidated elem.style.width = "100px"; elem.style.marginRight = "10px";elem.clientHeight // force layout here
But I want to mention the animation. Here we talk about JS animation, for example:
function animate (from, to) { if (from === to) return requestAnimationFrame(function () { from += 5 element1.style.height = from + "px" animate(from, to) })}animate(100, 500)
Every frame of an animation will lead to layout, which is unavoidable. However, to reduce the performance loss caused by layout, you can absolutely position the animation elements so that the animation elements are separated from the text stream, layout reduces the amount of computing.
Use requestanimationframe
Any operations that may cause re-painting should be placed inrequestAnimationFrame
In real projects, code is divided by module, and it is difficult to organize batch read/write as in the previous example. Then you can place the write operation inrequestAnimationFrame
In callback, the write operation is executed before the next painting.
// Readvar h1 = element1.clientHeight;// WriterequestAnimationFrame(function() { element1.style.height = (h1 * 2) + ‘px‘;});// Readvar h2 = element2.clientHeight;// WriterequestAnimationFrame(function() { element2.style.height = (h2 * 2) + ‘px‘;});
We can clearly observe the trigger time of the animation frame. The MDN is triggered before painting, but I guess it is executed before the JS script gives control to the browser for Dom invalidated check.
Other notes
In addition to performance issues caused by layout triggering, some other details are listed here:
Cache the results of the selector to reduce Dom queries. Htmlcollection is particularly mentioned here. Htmlcollection usesdocument.getElementByTagName
The obtained object type is similar to the array type, but each time an attribute of this object is obtained, it is equivalent to a DOM query:
var divs = document.getElementsByTagName("div"); for (var i = 0; i < divs.length; i++){ //infinite loop document.body.appendChild(document.createElement("div"));}
For example, the above Code will lead to infinite loops, so some caching is required when processing htmlcollection objects.
In addition, reducing the depth of Dom element nesting and optimizing CSS, removing useless styles is helpful for reducing the amount of layout computing.
During Dom query,querySelector
AndquerySelectorAll
It should be the final choice. They have the most powerful functions, but the execution efficiency is very poor. If possible, try other methods.
The following two jsperf links can compare the performance.
1) https://jsperf.com/getelementsbyclassname-vs-queryselectorall/162
2) http://jsperf.com/getelementbyid-vs-queryselector/218
Your own thoughts on the view layer
There are many theoretical aspects of the above content. from a practical perspective, the content discussed above is exactly what needs to be handled at the view layer. There is already a library named fastdom to do this, but its code is as follows:
fastdom.read(function() { console.log(‘read‘);});fastdom.write(function() { console.log(‘write‘);});
The problem is obvious.callback hell
And we can see that imperative code like fastdom lacks scalability. The key is to userequestAnimationFrame
Then it becomes a problem of asynchronous programming. To synchronize the read/write status, you must write a wrapper based on the Dom to internally control asynchronous read/write, I think we can consider directly accessing react ......
In short, try to avoid the problems mentioned above, but if you use a library, such as jquery, the layout problem lies in the abstraction of the library. Like using react to introduce its own component model, virtual Dom has been used to reduce Dom operations, and layout can be used only once each time the state changes. I don't know if it is used internally.requestAnimationFrame
And so on, it is quite difficult to do a view layer, and then prepare to learn the react code. I hope that I will come back to this question in a year or two, so I can have some new insights.
Why is Dom slow?