React and Vue, as the main front-end development frameworks in China, must be familiar to everyone in daily development. There is no denying that they have greatly improved our development efficiency and made our code more maintainable, but after the “clever” use of them, if you are a technical person, should you take a look at some of the ideas behind these frameworks? If not, it doesn’t matter, let’s do it together!

The entire code has been uploaded to github🐶

Virtual DOM

Intuitively, the virtual DOM is actually a data structure representing the real DOM structure. Use it reason is that frequent DOM will make the performance degradation of site operation, in order to guarantee performance, we need to make the operation of the DOM lean as far as possible, we can, through the method of virtual operation DOM, and comparing the difference between the old and the new node and accurate access to the smallest, most necessary of DOM collection, finally mounted on real DOM. Because manipulating data structures is much faster than directly modifying DOM nodes, our actual DOM manipulation is at best just a little bit at the end, right

How do I represent the DOM structure

This is the DOM structure of a list, and let’s analyze it. The information that needs to be included in it is

1. Label type UL, LI…

2. Tag attributes class,style…

3. Children node ul->li Li ->text…

No matter how complex the structure is, it is similar. So what should we do when we find the commonality of DOM structures

As you can see from this picture, we can easily represent it with object JS objects, and several properties are very easy to understand

  • TagName corresponds to the actual tag type
  • Attrs represents all attributes on the node
  • Child indicates the child node of the node

Can we give the virtual DOM a class like this

Function newElement(tag,attr,child){return newElement(tag,attr,child)}Copy the code

Test the

Now that the virtual DOM has been created, how do you mount it to the real DOM

Generate real DOM nodes

First we’ll need a method that sets the tag properties based on the object properties

We then add the Render method that creates the node inside the class

At this point we can create the actual DOM node by using the Render method. Inside the method, we set the property by calling the SetVdToDom method, and then type the child nodes recursively to the last remaining text node.

Finally, we render the DOM to the browser using a renderDom method

//vdmock.js part const VdObj1 = newElement('ul',{id: 'list'},[newElement('li',{class: 'list-1',style:'color:red' }, ['lavie']), newElement('li',{class: 'list-2' }, ['virtual dom']), newElement('li',{class: 'list-3' }, ['React']), newElement('li',{class: 'list-4' }, ['Vue']) ]) const RealDom = VdObj1.render() const renderDom = function(element,target){ target.appendChild(element) } export default function start(){ renderDom(RealDom,document.body) } // index.html <! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, Initial-scale =1.0"> <script type="module" SRC ="./vdmock.js" ></script> <title>Document</title> </head> <body >< script type="module" > import start from './vdmock.js' start() </script> </body> </html>Copy the code

The results are as follows:

Virtual DOM diff

Using the above method, we can easily generate the virtual DOM and render it to the browser. How can we calculate the difference between the virtual DOM and the virtual DOM after the user performs the operation? Here is the diff algorithm

We recursively compare the nodes and store the changes and return them by passing the old and new nodes to diff. Let’s implement getDiff

Gets the least difference array

const REMOVE = 'remove' const MODIFY_TEXT = 'modify_text' const CHANGE_ATTRS = 'change_attrs' const TAKEPLACE = 'replace' let initIndex = 0 const getDiff = (oldNode,newNode,index,difference)=>{ let diffResult = [] // The new node does not exist if the node has been deleted. newNode){ diffResult.push({ index, type: Else if(typeof newNode === 'string' && typeof oldNode === 'string'){if(oldNode! == newNode){ diffResult.push({ index, value: newNode, type: MODIFY_TEXT})} // If the node type is the same then continue to compare whether the attribute is the same}else if(oldNode.tagName === newNode.tagName){let storeAttrs = {} for(let key in oldNode.attrs){ if(oldNode.attrs[key] ! == newNode.attrs[key]){ storeAttrs[key] = newNode.attrs[key] } } for (let key in newNode.attrs){ if(! OldNode. Attrs. HasOwnProperty (key)) {storeAttrs [key] = newNode [key]}} / / determine whether there are different if (Object. The keys (storeAttrs). Length > 0) { diffResult.push({ index, value: storeAttrs, type: CHANGE_ATTRS})} oldNode.child.forEach((child,index)=>{// Deep traversal so keep index GetDiff (child,newNode.child[index],++initIndex,difference)}) else if(oldNode.tagName! TagName){diffresult. push({type: TAKEPLACE, index, newNode})} oldNode){ diffResult.push({ type: TAKEPLACE, newNode }) } if(diffResult.length){ difference[index] = diffResult } }Copy the code

The test results are as follows:

Update the dom

Now that we’ve generated two virtual DOM’s and saved the differences between the two DOM’s as objects, we’ll use these to update the differences to the real DOM!

The pace function recurses itself, updating the current node’s differences with Dofix

const doFix = (node,difference) =>{ difference.forEach(item=>{ switch (item.type){ case 'change_attrs': const attrs = item.value for( let key in attrs ){ if(node.nodeType ! == 1) return const value = attrs[key] if(value){ SetVdToDom(node,key,value) }else{ node.removeAttribute(key) } } break case 'modify_text': node.textContent = item.value break case 'replace': let newNode = (item.newNode instanceof Element) ? item.newNode.render(item.newNode) : document.createTextNode(item.newNode) node.parentNode.replaceChild(newNode,node) break case 'remove' : node.parentNode.removeChild(node) break default: break } }) }Copy the code

Everything is there, so let’s test it out!

const VdObj1 = newElement('ul',{id: 'list'},[
    newElement('li',{class: 'list-1',style:'color:red' }, ['lavie']),
    newElement('li',{class: 'list-2' }, ['virtual dom']),
    newElement('li',{class: 'list-3' }, ['React']),  
    newElement('li',{class: 'list-4' }, ['Vue']) ,
])
const VdObj = newElement('ol',{id: 'list'},[
    newElement('h2',{class: 'list-1',style:'color:green' }, ['lavieee']),
    newElement('li',{class: 'list-2' }, ['virtual dom']),
    newElement('li',{class: 'list-3' }, ['React']), 
    newElement('li',{class: 'list-4' }, ['Vue']) ,
    newElement('li',{class: 'list-5' }, ['Dva']) ,
    newElement('li',{class: 'list-5' }, ['Dva']) 
 
])
const RealDom = VdObj1.render()
const renderDom = function(element,target){
    target.appendChild(element)
}
export default function start(){
   renderDom(RealDom,document.body)
   const diffs = diff(VdObj1,VdObj)
   fixPlace(RealDom,diffs)
}
Copy the code

before

diff after

Hee hee perfect

Through these several examples, in fact, the idea of virtual DOM can be realized. If we can sort out the core concepts in the process of using the framework, we will certainly walk more steadily.

2020.2.12 update

Fixed dofix function and getdiff function code, some problems with the previous ~~

Finally, I wish everyone a happy New Year, the Year of the Ox offer to get soft, pay raise do not work overtime 🐶!!!!