Before we talk about virtual DOM, let’s talk about the browser rendering process.
How does the browser render the page
As a web front-end code farmers, every day in contact with the browser. In the long run we’re going to wonder, how does the browser parse our code and render it? Understanding how browsers render is important for performance optimization in our daily front-end development.
So today we’re going to take a closer look at how browsers render DOM.
Browser rendering process
First, the browser resolves the domain name of the requested URL, sends a request to the server, receives resources (HTML, CSS, JS, Images), and so on, and then the browser resolves the following:
- Parse the HTML document to generate a DOM Tree
- After the CSS style file is loaded, the CSS Rule Tree is parsed and constructed
- After the Javascript script file is loaded, the DOM API and CSSOM API are used to modify the DOM Tree and CSS Rule Tree
After parsing the above steps, the browser will use the DOM Tree and CSS Rule Tree to build the Render Tree.
Layout according to render tree to calculate geometric information for each node.
Finally, each node is drawn to the page.
HTML parsing
<html> <html> <head> <title>Web page parsing</title> </head> <body> <div> <h1>Web page parsing</h1> <p class="text">This is an example Web page.</p> </div> </body> </html>Copy the code
So the parse DOM tree looks like this
CSS analytical
/* rule 1 */ div { display: block; text-indent: 1em; }
/* rule 2 */ h1 { display: block; font-size: 3em; }
/* rule 3 */ p { display: block; }
/* rule 4 */ [class="text"] { font-style: italic; }
Copy the code
CSS Rule trees are generated in the same way as DOM trees. Note that CSS rules match DOM rules. Many people think CSS matches DOM trees quickly, but it’s not.
The style system starts at the right-most selector and moves to the left to match a rule. The style system matches the selector to the left until the rule is matched or stops matching due to an error.
This raises the question, why parse CSS from right to left?
To match efficiency.
All style rules are likely to be numerous, and most won’t match the current DOM element, so it’s extremely important to have a quick way to tell that “this selector doesn’t match the current element.”
If we do forward parsing, such as “div, div, p em”, we first need to check the entire path of the current element to the HTML, find the top div, and then look down. If there is no match, we have to go back to the top div, go down to match the first div in the selector, and backtrack a few times to determine whether it matched. It’s inefficient.
Here’s an example:
<div>
<div class="jartto">
<p><span> 111 </span></p>
<p><span> 222 </span></p>
<p><span> 333 </span></p>
<p><span class='yellow'> 444 </span></p>
</div>
</div>
<div>
<div class="jartto1">
<p><span> 111 </span></p>
<p><span> 222 </span></p>
<p><span> 333 </span></p>
<p><span class='red'> 555 </span></p>
</div>
</div>
div > div.jartto p span.yellow{
color:yellow;
}
Copy the code
For the above example, if you search from left to right:
1. Find all div nodes;
2. Find all child divs in div node with class = “jartto”
3. Then match p span. Yellow, etc.;
4. In the case of a mismatch, you must go back to the div or P node you first searched for, then search for the next node, and repeat the process.
If you read CSS rules from left to right, most of the rules won’t match until you get to the end (right), which is time consuming and energy consuming, and many of them end up useless. If the right-to-left approach is adopted, then as long as the right-most selector does not match, it can be discarded directly, avoiding many invalid matches.
So the browser CSS matches the core algorithm by matching nodes from right to left. This is to reduce the number of invalid matches, thus faster matching and better performance.
CSS matching HTML elements is a fairly complex and performance problem. So, you’ll see a lot of people telling you in more than N places, DOM tree should be small, CSS should use ID and class, do not transition layer on layer…
Building a Render tree
After running the Javascript script, the final DOM Tree and CSS Rule Tree are parsed. From these two, we can synthesize our Render Tree, which includes all the visible DOM content on the web page, as well as all the CSSOM style information for each node.
To build the render tree, the browser basically does the following:
- Each visible node is traversed starting at the root of the DOM tree.
- Some nodes are not visible (such as script tags, meta tags, etc.) and are ignored because they do not show up in the render output.
- Some nodes are hidden by CSS and thus ignored in the render tree. For example, the SPAN node in the example above — which does not appear in the render tree — is set to display: None by an explicit rule.
- For each visible node, find suitable CSSOM rules for it and apply them.
- Outputs the visible node, along with its contents and computed style.
Considerations for rendering
Repaint and Reflow are the two main factors that affect browser rendering:
- Repaint– Repaint. A part of the screen is redrawn, such as a DOM element whose background color has changed but whose position size has not.
- Reflow– Reflow, which means that the geometry of the element (position, width, height, hiding, etc.) has changed and we need to revalidate and evaluate the Render Tree. Some or all of Render Tree has changed. As you can see, our Reflow cost is much higher than Repaint, which might be fine on some high performance computers, but if Reflow happens on mobile phones, the process is very painful and power-consuming. This is also a barrier to using JQuery on mobile pages. ,
Let’s look at some javascript code:
var bstyle = document.body.style; // cache bstyle.padding = "20px"; // reflow, repaint bstyle.border = "10px solid red"; // Reflow and repaint bstyle.color = "blue"; // repaint bstyle.backgroundColor = "#fad"; // repaint bstyle.fontSize = "2em"; // reflow, repaint // new DOM element - reflow, repaint document.body.appendChild(document.createTextNode('dude! '));Copy the code
Of course, our browser is smart, it doesn’t reflow or repaint every time you change styles like above. Typically, browsers accumulate a batch of these operations and do a reflow, also known as asynchronous or incremental asynchronous reflow.
While the browser will help us optimize reflow operations, there are several ways to reduce reflow operations during actual development
Reduce reflow/repaint methods
-
Don’t change the STYLE of the DOM line by line. Instead, define the CSS class in advance and change the DOM className.
// bad var left = 10, top = 10; el.style.left = left + “px”; el.style.top = top + “px”;
// Good el.className += ” theclassname”;
// Good el.style.cssText += “; left: ” + left + “px; top: ” + top + “px;” ;
2) Take the DOM offline and modify it. Such as:
- Manipulate the DOM in memory using the documentFragment object
- First give the DOM to display: None (once reflow) and then change it however you want. Like, 100 times, and then it shows up.
- Clone a DOM node into memory and change it in any way you want. When you’re done, swap it with the one on line.
3) Do not place DOM node attributes in a loop as variables in the loop. Otherwise this will result in a lot of reading and writing of the node’s properties.
4) Never use a table layout. Because a small change can cause the entire table to be rearranged.
5) Modify the lower-level DOM as much as possible. Of course, changing the DOM at the bottom of the hierarchy can cause large reflows, but it can also have small effects.
Virtual DOM
What is the Virtual DOM?
Most front-end developers are familiar with the term Virtual DOM, which simply means creating a buffer layer between data and the real DOM. When the rendering is triggered by data changes, it is not directly updated to the DOM. Instead, it is transformed into a Virtual DOM and compared with the Virtual DOM obtained last time. Changes are found in the rendered Virtual DOM, and then the changes are updated to the real DOM.Copy the code
Why is Virtual DOM fast?
1) DOM structure is complex and operation is slow
We’re typing on the console
var div = document.createElement('div')
var str = ''
for (var key in div) {
str = str + key + "\n"
}
console.log(str)
Copy the code
You can easily see that an empty DIV object has hundreds of attributes, so it’s understandable that the DOM is slow. It’s not that browsers don’t want to implement DOM properly, it’s that DOM design is too complex.
2) JS computes quickly
julialang.org/benchmarks/
Julia has a Benchmark, Julia Benchmarks, which shows that Javascript is very close to C, only a few times different, and roughly the same magnitude as Java, too. This shows how fast simple Javascript can run.
Compared to the DOM, our native JavaScript objects are much faster and simpler to work with.
We can easily represent it with JavaScript objects.
Var olE = {tagName: 'ul', // 标 名 : props: {// 标 名 : props: {// 标 名 : props: {// 标 名 : props: {// 标 名 : props: {// 标 名 : props: {// 标 名 : props: {// 标 名 : props: { 'li', props: {class: 'item'}, children: ["Item 1"]}, {tagName: 'li', props: {class: 'item'}, children: ["Item 2"]}, {tagName: 'li', props: {class: 'item'}, children: ["Item 3"]}, ] }Copy the code
The corresponding HTML is:
<ul id='ol-list'>
<li class='item'>Item 1</li>
<li class='item'>Item 2</li>
<li class='item'>Item 3</li>
</ul>
Copy the code
So, since we can represent the DOM in javascript, This means that we can use JavaScript to construct our real DOM tree. When our DOM tree needs to be updated, we first render the Virtual DOM tree constructed by JavaScript and then update it to the real DOM tree.
So the Virtual DOM algorithm is:
The DOM tree structure is first represented by JavaScript object structure. Then use this tree to build a real DOM tree and insert it into the text
File. When the state changes, a new object tree is rebuilt. Then compare the differences between the two trees using the new tree and the old tree.
The differences are then updated to the old tree, and the entire change is finally written to the real DOM.
Simple Virtual DOM algorithm implementation
Step 1: Use JS objects to simulate the DOM tree, and build
Representing a DOM node in JavaScript is a simple matter. You just need to record its node type, attributes, and child nodes:
// create a virtual DOM function function Element (tagName, props, Children) {this.tagName = tagName // tagName this.props = props module.exports = function (tagName, props, children) { return new Element(tagName, props, children) }Copy the code
Practical applications are as follows:
Var ul = el('ul', {id: 'list'}, [el('li', {class: 'item'}, ['Item 1']), el('li', {class: 'item'}, ['Item 2']), el('li', {class: 'item'}, ['Item 3']) ])Copy the code
Ul is now just a DOM structure represented by a JavaScript object that does not exist on the page. We can build real based on this UL
-
Chemical element:
Prototype = function () {var el = document.createElement(this.tagname) Props = this. Props for (var propName in props) {// Set the DOM attribute of the object var propValue = props[propName] el.setAttribute(propName, propValue) } var children = this.children || [] children.forEach(function (child) { var childEl = (child instanceof Element) ? Child.render () // If the child node is also a virtual DOM, recursively build the DOM node: Document.createtextnode (child) // If a string, build only the text node el.appendChild(childEl)}) return el}Copy the code
Our render method builds a real DOM node based on the tagName, sets the node attributes, and recursively builds the child elements:
Var ulRoot = ul. Render () / / js building dom object to render to build the document. The body. The appendChild (ulRoot) / / real dom object into the bodyCopy the code
So we have ul and LI DOM elements in the body
<body>
<ul id='list'>
<li class='item'>Item 1</li>
<li class='item'>Item 2</li>
<li class='item'>Item 3</li>
</ul>
</body>
Copy the code
Step 2: Compare the differences between the two virtual DOM trees
Here we assume that some state or some data has been modified for us, which will result in a new virtual DOM
/ / new DOM var ol = el (' ol '{id:' ol - list '}, [el (' li ', {class: 'ol - item'}, [' item 1]), el (' li '{class: 'ol-item'}, ['Item 2']), el('li', {class: 'ol-item'}, ['Item 3']), el('li', {class: 'ol - item'}, [' item 4]]) old DOM var / / ul = el (' ul '{id:' list '}, [el (' li '{class: 'item'}, ['Item 1']), el('li', {class: 'item'}, ['Item 3']), el('li', {class: 'item'}, ['Item 2']) ])Copy the code
So we’re going to compare it to the virtual DOM tree that we just generated last time.
As we all know, the core part of the Virtual DOM algorithm is the comparison of differences, which is called the Diff algorithm.
Because there is very little movement across hierarchies.
Diff algorithms, in general, compare the same level to the same level
Var patch = {'REPLACE' : 0, // REPLACE' REORDER' : 1, // add, delete, move 'PROPS' : 2, // attribute change 'TEXT' : 3 // TEXT content change}Copy the code
For example, if the div above is different from the new div and the current tag is 0, then:
// Each array represents the difference of one element [{difference}, {difference}], [{difference}, {difference} ] ] patches[0] = [ { type: REPALCE, node: newNode // el('section', props, children) }, { type: PROPS: {id: "container"}}, {type: REORDER, moves: [{index: 2, item: item, type: 1}, // Reserved nodes {index: {index: 1, item: item, type: 1}}]; If the content of a TEXT node is modified, record patches[2] = [{type: TEXT, content: "I am the newly modified TEXT content"}] // View the diff.js algorithm for detailsCopy the code
Each difference has a different comparison method. After comparison, differences are recorded and applied to the real DOM, and the latest virtual DOM tree is saved for next comparison.
Step 3: Apply the difference to a real DOM tree
After comparison, we already know which nodes are different, and we can easily make minimal changes to the real DOM.
// See patch.js for detailsCopy the code
Found the problem
There’s a problem with this. Isn’t Virtual DOM faster? But eventually you have to do DOM manipulation, right? What’s the point? It would have been easier to just do DOM manipulation from the start.
So here we want to have a correct understanding of Virtual DOM
React is faster than the REAL DOM.
Chrisharrington. Making. IO/demos/perfo…
The optimal changes
What Virtual DOM algorithms can guarantee you is that every DOM manipulation is algorithmically optimal, which is not guaranteed if you manipulate the DOM yourself.
The second
Changes to development patterns
To allow developers to focus on manipulating data rather than taking over DOM operations. Virtual DOM allows us to ignore the complex DOM structure in the actual development process, but only the state and data binding DOM structure, which is a great development step