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 🐶!!!!