Why does manipulating the DOM take time
thread
First of all, we need to know that browsers are multi-process architecture. We can think of each page as a renderer process, and the renderer process is multi-threaded, including JavaScript engine threads, rendering engine threads, etc.
Rendering engine threads and JavaScript engine threads are mutually exclusive, meaning that only one engine thread is running at any one time and the other is blocked. In addition, in the process of thread switching, the state information of the previous thread needs to be saved and the state information of the next thread needs to be read, which is context switching. This operation is time-consuming.
const times = 1000000;
console.time('dom')
for(let i=0; i<times; i++){
let temp = document.body // Loop through the body element
}
console.timeEnd('dom') Dom: / / 25.093994140625 ms
console.time('object')
let obj = document.body // Save the body as a js variable
for(let i=0; i<times; i++){
let temp = obj
}
console.timeEnd('object') / / object: 2.39892578125 ms
Copy the code
For example, in the above code, each loop read involves a context switch in the first example, which takes longer than the second example involving only JavaScript engine threads
To render
When manipulating the DOM, changes to elements and styles will cause the rendering engine to re-render, making backflow and redrawing possible. Specific can see the browser render the article. Here are the definitions of reflux and redraw
Backflow occurs when we trigger geometry changes in the DOM. Backflow is triggered if any of the following operations occur:
- Add, delete, or move DOM nodes, for example, display
- The geometric attributes of DOM nodes are modified. Common geometric attributes include width, height, margin, padding, border and font-size
- When reading and writing offset, Client, or Scroll attributes, the browser will perform backflow to obtain accurate values
- Execute the window.getComputedStyle method
Redraw is triggered when we cause a change in the style of the DOM node, but not in the geometry.
- Changes to color styles, such as color, border-style, backgro0und-color, etc
- Hide nodes from styles without changing the DOM structure, such as visibility
Take a chestnut
Chrome provides performance analysis tools to analyze the difference in rendering time between backflow and redraw
In the first example, we trigger backflow by modifying the margin of the element after clicking the button.
const times = 100000;
let html = '<button id="btn">change</button>'
for(let i=0; i<times; i++){
html += `<li>${i}</li>`
}
document.body.innerHTML = html;
const btn = document.getElementById('btn')
btn.addEventListener('click'.() = > {
const lis = document.querySelectorAll('li')
lis.forEach((item, index) = > {
item.style.margin = index % 2 ? '20px' : '5px'})})Copy the code
In the second example, we trigger a redraw by changing the color of the element after clicking the button.
const times = 100000;
let html = '<button id="btn">change</button>'
for(let i=0; i<times; i++){
html += `<li>${i}</li>`
}
document.body.innerHTML = html;
const btn = document.getElementById('btn')
btn.addEventListener('click'.() = > {
const lis = document.querySelectorAll('li')
lis.forEach((item, index) = > {
item.style.color = index % 2 ? 'blue' : 'red'})})Copy the code
From the two results, the Rendering time of reflow (Rendering + Painting) is significantly higher than that of repainting
How can I manipulate the DOM efficiently
Batch operation element
Operations on elements are executed in batches together
console.time('time')
let body = document.body
for(let i=0; i<3000; i++){
body.innerHTML += `<div></div>`
}
console.timeEnd('time') / / time: 4684.2490234375 ms
Copy the code
For example, if you create 3000 elements, you can use variables to save and then batch update all the elements to the target element
console.time('time')
let html = ""
for(let i=0; i<3000; i++){
html += `<div></div>`
}
document.body.innerHTML = html
console.timeEnd('time') / / time: 2.4091796875 ms
Copy the code
Instead of modifying the innerHTML directly, you can create a container to hold the elements, and then add the container to the target element. The DocumentFragment can therefore be used to create containers that are not part of the real DOM and whose changes do not trigger DOM re-rendering
let content = document.createDocumentFragment()
for(let i=0; i<3000; i++){
let cur = document.createElement('div')
cur.innerText = i
content.appendChild(cur)
}
document.body.appendChild(content)
Copy the code
Cache element variables
Caching element variables is the same as reducing context switches.
let html = ' '
for(let i=0; i<10000; i++){
html += `<div></div>`
}
document.body.innerHTML = html;
console.time('time')
for(let i=0; i<document.querySelectorAll('div').length; i++){
document.querySelectorAll('div')[i].innerText = i
}
console.timeEnd('time') / / time: 3018.7900390625 ms
Copy the code
For example, in this example, each loop will fetch the selector. So we can use a variable to cache these selectors
let html = ' '
for(let i=0; i<10000; i++){
html += `<div></div>`
}
document.body.innerHTML = html;
console.time('time')
let query = document.querySelectorAll('div')
for(let i=0; i<query.length; i++){
query[i].innerText = i
}
console.timeEnd('time') / / time: 7.298828125 ms
Copy the code
Avoid using style too often
Not only does frequent use of style involve frequent manipulation of the DOM, it also takes more time to trigger a style evaluation than using class.
Here’s an example of how style is used frequently, and again, we use Perform to record performance after pressing the button
let html =
for(let i=0; i<100000; i++){
html += `<div id="i${i}">${i}</div>`
}
document.body.innerHTML = html
document.getElementById('btn').addEventListener('click'.function a(){
for(let i=0; i<100000; i++){
let cur = document.getElementById(`i${i}`)
cur.style.height = '30px';
cur.style.color = 'red';
cur.style.border = '1px';
cur.style.margin = '1px';
cur.style.padding = '1px'; }})Copy the code
At this point, we use class instead of style to observe the performance difference.
let html =
for(let i=0; i<100000; i++){
html += `<div id="i${i}">${i}</div>`
}
document.body.innerHTML = html
document.getElementById('btn').addEventListener('click'.function a(){
for(let i=0; i<100000; i++){
document.getElementById(`i${i}`).className = 'cls'}})Copy the code
From the above changes, we can see that JavaScript time and Rendering time are reduced.
In particular, Rendering takes less time to calculate styles, and no difference is made in layout and layer tree updating.