1. Redraw and rearrange

1.1 What are redraws and rearrangements

A redraw is a style change that leaves the position and size of an element unchanged. A rearrangement is when the position or size of an element has changed, the browser needs to recalculate the render tree, and when a new render tree is created, the browser redraws the affected elements.

1.2 Rendering the Browser Page

One of the questions you get when you go to an interview is, “What happens if YOU type a url into a browser?” “The answer to this question involves not only web knowledge but also browser rendering of the page. When our browser receives the page response from the server, it will start to Render line by line. When it encounters CSS, it will asynchronously calculate the property value, and then continue to parse down the DOM to form a DOM tree. After parsing the DOM, it will combine the asynchronously calculated style (style box) with the DOM tree to become a Render tree. It is then drawn on the page by the browser. The DOM tree differs from the Render tree in that the style is display: None; The nodes of the DOM tree are not in the render tree. After the browser draws, it begins to parse the JS file and determine whether to redraw and rearrange according to js.

1.3 Causes of redrawing and rearrangement

Factors that produce redrawing:

  • Change style attributes such as visibility, outline, and background color without changing element size, location, and so on. The browser redraws the element based on the new attributes.

2. A factor that produces a rearrangement:

  • Content change
  • Text changes or image size changes
  • Changes in the geometric properties of DOM elements
    • For example, when the width and height values of DOM elements are changed, relevant nodes in the original rendering tree will become invalid, and the browser will rearrange relevant nodes in the rendering tree according to the changed DOM. If the geometric properties of the parent node change, the position of the child node and subsequent sibling nodes will be recalculated, resulting in a series of rearrangements.
  • Changes in the structure of the DOM tree
    • Adding A DOM node, changing the position of a DOM node, and deleting a node are all changes to the DOM tree that cause a reorder of the page. The browser layout is a top-down process. Modifying the current element does not affect the elements that have been traversed before it, but if a new element is added in front of all nodes, all subsequent elements are rearranged.
  • Get some properties
    • In addition to direct changes to the rendered tree, when retrieving some property values, the browser also rearranges to get the correct value. These properties include: OffsetTop, offsetLeft, offsetWidth, offsetHeight, scrollTop, scrollLeft, ClientTop, clientLeft, clientWidth, clientHeight, getComputedStyle().
  • Browser window size changed
    • The change of the window size will affect the change of the size of the elements in the whole web page, that is, the change of the set attributes of DOM elements, so it will cause rearrangement.
  • The presence of a scrollbar (which triggers a reorder of the entire page)

Anyhow you should know, js is single-threaded, redrawn and rearrangement will block the user’s operation and influence the performance of the page, when a page redrawn and rearrangement have occurred many times such as writing a timer every 500 ms change elements on the page width is high, then this page is likely to become more and more caton, we should as far as possible to reduce redrawn and rearrangement. This is where we started optimizing the DOM.

2, to optimize

2.1 Reducing Access

Reducing access times is a natural way to cache elements, but be careful

var ele = document.getElementById('ele');
Copy the code

In this way, ele is not cached. Every call to ele is equivalent to a visit to the node whose ID is ele.

2.1.1 cache NodeList

var foods = document.getElementsByClassName('food');
Copy the code

We can use foods[I] to access the ith class food element, but instead of an array, foods is a NodeList. A NodeList is a class array that holds an ordered number of nodes and can be accessed by location. NodeList objects are dynamic, and each access runs a document-based query. To minimize the number of accesses to NodeList, consider caching the value of NodeList.

/ / before optimization
var lis = document.getElementsByTagName('li');

for(var i = 0; i < lis.length; i++) {
     // do something...  
}

// After optimization, the length value is cached so that the length value is not queried every time
var lis = document.getElementsByTagName('li');

for(var i = 0, len = lis.length; i < len; i++) {
     // do something...  
}
Copy the code

Moreover, because NodeList changes dynamically, if not cached, it may cause an infinite loop, such as adding elements while obtaining the length of NodeList.

2.1.2 Change selector

The two most common ways to get an element are getElementsByXXX() and queryselectorAll(). The two selectors are very different. The former gets a dynamic collection, while the latter gets a static collection.

// Let's say we start with two Li's
var lis = document.getElementsByTagName('li');  // Dynamic sets
var ul = document.getElementsByTagName('ul') [0];
 
for(var i = 0; i < 3; i++) {
    console.log(lis.length);
    var newLi = document.createElement('li'); 
    ul.appendChild(newLi);
}
// Output: 2, 3, 4
/ / after optimization
var lis = document.querySelectorAll('li');  // Static sets
var ul = document.getElementsByTagName('ul') [0];
 
for(var i = 0; i < 3; i++) {
    console.log(lis.length);
    var newLi = document.createElement('li'); 
    ul.appendChild(newLi);
}
// Output: 2, 2, 2
Copy the code

Operations on static collections do not result in re-querying documents and are more optimized than dynamic collections.

2.1.3 Avoid unnecessary loops

/ / before optimization
for(var i = 0; i < 10; i++) {
    document.getElementById('ele').innerHTML += 'a'; 
} 
/ / after optimization
var str = ' '; 
for(var i = 0; i < 10; i++) {
    str += 'a'; 
}
document.getElementById('ele').innerHTML = str;
Copy the code

The optimized code accesses ele element 10 times, while the optimized code accesses ele element only once, which greatly improves the efficiency.

2.1.4 Event delegate

Event functions in JS are all objects. If there are too many event functions, a large amount of memory will be occupied. In addition, more DOM elements bound to events will increase the number of DOM accesses and delay the interaction ready time of the page. Hence the birth of the event delegate, which takes advantage of the event bubble by specifying only one event handler to manage all events of a certain type.

// Before event delegate
var lis = document.getElementsByTagName('li');
for(var i = 0; i < lis.length; i++) {
   lis[i].onclick = function() {
      console.log(this.innerHTML);
   };  
}    

// After the event is delegated
var ul = document.getElementsByTagName('ul') [0];
ul.onclick = function(event) {
   console.log(event.target.innerHTML);
};
Copy the code

Before the event delegate we access lis. Length times li, but after the event delegate we only access ul once.

2.2 Reduced redrawing and rearrangement

2.2.1 Changing multiple styles of a DOM node

We want to change the width and height of a div element

var div = document.getElementById('div1');
div.style.width = '220px';
div.style.height = '300px';
Copy the code

The above operation changes two attributes of the element, accesses the DOM three times, and triggers two rearrangements and two redraws. We said optimization is to reduce the number of accesses and the number of redraws and rearranges, so from that point of view can we only access the element once and reduce the number of rearranges to 1? Obviously, we can, we can write a class in CSS

/* css .change { width: 220px; height: 300px; } * /
document.getElementById('div').className = 'change';
Copy the code

This allows you to manipulate multiple styles at once

2.2.2 Changing DOM Node Styles in Batches

In the case of the above code for a DOM node, what if we want to change the style of a DOM collection? The first thing that comes to mind is to walk through the collection, giving each node a className. Don’t you have to visit multiple DOM nodes? Consider the difference between a DOM tree and a render tree from the beginning of this article. If a node’s display property is None, that node will not exist in the Render tree. This means that operations on that node will not affect the Render tree and will not cause redrawing or rearranging.

  • Set the parent element of the collection to be modified display: none;
  • It then iterates over the modified collection nodes
  • Display: block;
// Suppose the class is increased to.change
var lis = document.getElementsByTagName('li');  
var ul = document.getElementsByTagName('ul') [0];

ul.style.display = 'none';

for(var i = 0; i < lis.length; i++) {
    lis[i].className = 'change';  
}

ul.style.display = 'block';
Copy the code

2.2.3 DocumentFragment

The createDocumentFragment() method is used to create a virtual node object, or to create a document fragment node. It can contain various types of nodes and is empty when created.

The DocumentFragment node does not belong to the document tree, and the parentNode attribute is always null. A useful feature is that when a DocumentFragment node is requested to be inserted into the document tree, all of its descendants are inserted instead of the DocumentFragment itself. This feature makes the DocumentFragment a placeholder for nodes that are inserted at one time

In addition, when multiple DOM elements need to be added, if these elements are first added to the DocumentFragment and then the DocumentFragment is added to the page, The Times of rendering the DOM on the page can be reduced and the efficiency can be significantly improved.

Usage:

var frag = document.createDocumentFragment(); // Create a DOM fragment
for(var i=0; i<10000; i++) {var li = document.createElement("li");			
    li.innerHTML = i;		
    frag.appendChild(li);  // Add the li element to the document fragment
} 			
ul.appendChild(frag);  // Add the document fragment to ul
Copy the code

3, summarize

  • Reduce dom access
    • Cache node attribute values
    • The use of selectors
    • Avoid unnecessary loops
    • Event delegation
  • Reduce redrawing and rearrangement
    • Use className to change multiple styles
    • Remove the parent element from the document flow and recover
    • DocumentFragment

I will update if I see other optimization schemes in the future. Welcome to communicate with me.

Reference documents:

  • Exploration of high-frequency DOM operations and page performance optimization
  • High-performance JS – DOM