An overview of the

React in the presence of the diff algorithm can greatly reduce the browser modify the Dom performance losses, the diff work out need to modify the minimum node, the algorithm and the node to replace, not attached to the parent node with the modification, operation data structure is always faster than operating the Dom, so can reduce a lot less to Dom manipulation, To improve performance

Virtual dom

Attrs (classname,title, etc.) 3. Child (classname,title, etc.) Of course it can. After all, everything is the same. Let’s implement a virtual DOM class

Export class Element{constructor(tagName,attrs = {},child = []){// Attrs is a string of key/value pairs this.attrs = attrs this.child = child }}Copy the code

Then we implement a constructor

export function newElement(tag,attr,child){
    return new Element(tag,attr,child)
}
Copy the code

We then construct a DOM structure from our constructor

const VdObj1 = newElement('ul',{id: 'list'},[
    newElement('li',{class: 'list',style:'color:red' }, ['clhhh']),
    newElement('li',{class: 'list',style:'color:red' }, ['js']),
    newElement('li',{class: 'list' }, ['onclick']),  
    newElement('li',{class: 'list' }, ['onclick']) 
])
Copy the code

Ok, console.log(VdObj1); it

Element {//tagName: 'ul', attrs: {id: 'list'}, child: [Element {tagName: 'li', attrs: [Object], child: [Array] }, Element { tagName: 'li', attrs: [Object], child: [Array] }, Element { tagName: 'li', attrs: [Object], child: [Array] }, Element { tagName: 'li', attrs: [Object], child: [Array]}]// The attrs and child attributes in the child node are not printed.Copy the code

Now that we’ve created the virtual DOM, we’re ready to mount it onto the real DOM

Mount to the real DOM

First, change the atRRS object properties to tag properties.

 const function =SetVdtoDom(node,key,value){
     switch(key)
         case 'style':
         node.style.cssText=value
 }
 
Copy the code

Then implement the render function in the class to render

Export class Element{constructor(tagName,attrs = {},child = []){// Attrs is a string of key/value pairs Attrs = attrs this.child = child} render(){let ele =document.createElement(this.tagname)// create a document object let Attrs =this.attrs for(let key in attrs){SetVdtoDom(ele,key,attrs[key]) set attrs attribute} let childNodes=this.child childNodes.foreach(function(child){ let childEle = child instanceof Element ? render : Documen.createtextnode (Child) // Recurse if the child is an Element, End recursion ele. Append (childEle)// Add child node to parent node}) return ele}}Copy the code

At this time, the virtual DOM represented by JS has been mounted on the real DOM, especially recursion there do not understand the students should read several times oh. Although the Dom represented by JS is virtual, we also need to grasp it!

The diff algorithm

const diff=(oldNode,newNode) = >{
    let difference={} / / to a key object to represent the difference of the index value of [index, index, type: type, value: {newNode}]
    getdiff(oldNode,newNode,index,difference)
    return difference
}
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=[]
// If the new node does not exist
    if(! newNode){ diffResult.push( { index,type: REMOVE
            }
        )
    }
    // Replace the text node directly
    else if(typeof newNode==='string'&&typeof oldNode==='string'){
        diffResult.push({
            index,
            type:MODIFY_TEXT,
            newNode
        })      
    }
    // It is already a node
    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.attrs[key]console.log(storeAttrs);
        }
        if(Object.keys(storeAttrs).length>0){
            diffResult.push({
                index,
                type:CHANGE_ATTRS,
                value:storeAttrs
            })
        }
        oldNode.child.forEach((child,index) = >{
            getdiff(oldNode,newNode,initIndex++,difference)
        })
        // If the labels are different, replace them directly
    }else if(newNode.tagName! ==oldNode.tagName){ diffResult.push({ index,type:TAKEPLACE,
            value:newNode
        })
    }
     if(! oldNode){ diffResult.push({ index,type:TAKEPLACE,
            value:newNode
        })
    }
    if(diffResult.length){
        for(let key in diffResult){
        difference[key]=diffResult
    }
   
}
Copy the code

The diff algorithm is written in full code below

export class Element{ constructor(tagName,attrs = {},child = []){ this.tagName = tagName this.attrs = attrs this.child =  child } render(){ let ele = document.createElement(this.tagName) let attrs = this.attrs for(let key in attrs){ SetVdToDom(ele,key,attrs[key]) } let childNodes = this.child childNodes.forEach(function(child){ let childEle = child instanceof Element ? child.render() : document.createTextNode(child) ele.appendChild(childEle) }) return ele } } export function newElement(tag,attr,child){ return new Element(tag,attr,child) } export const SetVdToDom = function(node,key,value){ switch(key){ case 'style': node.style.cssText = value break // case 'value': // let tagName = node.tagName || '' // tagName = tagName.toLowerCase() // if(tagName === 'input' || tagName === 'textarea'){// notice input tags // node.value = value //}else{// node.setAttribute(key,value) //} // break // default: // node.setattribute (key,value) // break}} const diff = (oldNode,newNode)=>{let difference = {} // Saves the difference between two nodes getDiff(oldNode,newNode,0,difference) return difference } 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 the node has been deleted the if (! NewNode){console.log(" The node has been deleted "); 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] } } // console.log(storeAttrs); 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})} return if(diffresult.length){console.log(index); difference[index] = diffResult } } const fixPlace = (node,difference)=>{ let pacer = { index: 0} pace(node,pacer,difference)} /* Receive a real DOM (need to update the node), receive the minimum difference set after diff */ const pace = (node,pacer,difference) =>{let currentDifference = difference[pacer.index] let childNodes = node.childNodes // console.log(difference) childNodes.forEach((child)=>{ pacer.index ++ pace(child,pacer,difference) }) if(currentDifference){ doFix(node,currentDifference) } } 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 } }) } const VdObj1 = newElement('ul',{id: 'list'},[ newElement('li',{class: 'list',style:'color:red' }, ['clhhh']), newElement('li',{class: 'list',style:'color:red' }, ['js']), newElement('li',{class: 'list' }, ['onclick']), newElement('li',{class: 'list' }, ['onclick']) ]) const VdObj = newElement('ul',{id: 'list'},[ newElement('li',{class: 'list',style:'color:blue' }, ['clh']), newElement('li',{class: 'list' ,style:'color:grey'}, ['ts']), newElement('li',{class: 'list' }, ['onclick']), // newElement('li',{class: 'list-1' }, ['Vue']), // newElement('input',{value:'id' }, ['Vue']) ]) 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) console.log(diffs) fixPlace(RealDom,diffs) }Copy the code