SoFunction
Updated on 2025-04-04

Simple implementation of Virtual DOM that helps us operate efficiently

introduction

Before, I learned some ideas about Vue about Virtual DOM when I was looking at the source code of vue. Virtual DOM can help us operate DOM more efficiently. It implements a vnode js object, which corresponds to the dom node object one by one. Through our operations on vnode, we can implement the dom operation, so as to avoid the efficiency problems caused by frequent dom operations. Vue's Virtual DOM implements an efficient diff algorithm to quickly compare and update the dom tree.

The principles of Virtual DOM implementation of vue will be mentioned in the following article. For easy understanding and learning, I wrote a simple demo of Virtual DOM manipulation DOM tree. here it isComplete code and DOM

VNode

First, create a vnode object, and vnode records some properties of the corresponding DOM object.

export default class VNode {
    constructor (tag, nodeType,key, props, text, children){
         = tag //element type         = nodeType //node type, 1 is an ordinary node, 3 is a text node, 8 is annotation         = key
         = props //Node's properties         = text //The content of the text node         = children//Child node    }
    // Method of rendering vnode into DOM node    render(){
        var el
        if(===1){
            el = ()
            for(let prop in ){
                setAttr(el,prop,[prop])
            }
            if(){
                (function(ch,i){
                    (())
                })
            }
        } else if(===3){
            el = ()
        } else if(===8){
            el = ()
        }
         = 
        return el
    }
}
function setAttr(node,key,value){
    if(key==='style'){
        for(let val in value){
            [val] = value[val]
        }
    } else {
        (key,value)
    }
}

Diff

diff is mainly used to compare the differences between new and old vnodes, find out the different elements and record them on the directives object, so that the old vnode can be replaced by the directives content and draw the new DOM.

This is the entry method of diff, the parameters are the old vnode and the new vnode. Directives are objects used to record the changes of each node.

export default function diff(oldVNode, newVNode){
    directives = {}
    diffVNode(oldVNode,newVNode,directives)
    return directives
}

We call diffVNode in the diff method to compare nodes one by one. First, it compares whether oldVNode and newVNode are the same nodes. If the same is true, judge the node type and select the comparison method. For text and comment nodes, you only need to compare whether the text content is the same. For elements, you need to compare whether the element label, the element attributes and child elements are the same.

function diffVNode(oldVNode,newVNode){
    if(newVNode && isSameTypeNode(oldVNode,newVNode)){
        if(===3 || ===8){
            if( !== ){
                addDirectives(,{type:TEXT, content: })
            }
        } else if(===1){
            if( ===  &&  == ){
                var propPatches = diffProps(, )
                if((propPatches).length>0){
                    addDirectives(,{type:PROP, content: propPatches})
                }
                if( || )
                    diffChildren(,,)
            }
        }
    }
    return directives
}

This is a method to compare node attributes. For properties that have changed, we record the changed parts in the patches array.

function diffProps(oldProps,newProps){
    let patches={}
    if(oldProps){
        (oldProps).forEach((prop)=>{
            if(prop === 'style' && newProps[prop]){
                let newStyle = newProps[prop]
                let isSame = true
                (oldProps[prop]).forEach((item)=>{
                    if(prop[item] !== newStyle[item]){
                        isSame = false
                    }
                })
                if(isSame){
                    (newStyle).forEach((item)=>{
                        if(!(item)){
                            isSame = false
                        }
                    })
                }
                if(!isSame)
                    patches[prop] = newProps[prop]
            }
            if(newProps[prop] !== oldProps[prop]){
                patches[prop] = newProps[prop]
            }
        })
    }
    if(newProps){
       (newProps).forEach((prop)=>{
        if(!(prop)){
            patches[prop] = newProps[prop]
        }
    })
   }
    return patches
}

The following is a method to compare child nodes. The update of child nodes is divided into three operations: adding child nodes, deleting child nodes and moving child nodes. Operations for children will be recorded on the parent node's directives.

function diffChildren(oldChildren,newChildren,parentKey){
    oldChildren = oldChildren || []
    newChildren = newChildren || []
    let movedItem = []
    let oldKeyIndexObject = parseNodeList(oldChildren)
    let newKeyIndexObject = parseNodeList(newChildren)
    for(let key in newKeyIndexObject){
        if(!(key)){
            addDirectives(parentKey,{type:INSERT,index:newKeyIndexObject[key],node:newChildren[newKeyIndexObject[key]]})
        }
    }
    for(let key in oldKeyIndexObject){
        if((key)){
            if(oldKeyIndexObject[key] !== newKeyIndexObject[key]){
                let moveObj = {'oldIndex':oldKeyIndexObject[key],'newIndex':newKeyIndexObject[key]}
                movedItem[newKeyIndexObject[key]] = oldKeyIndexObject[key]
            }
            diffVNode(oldChildren[oldKeyIndexObject[key]],newChildren[newKeyIndexObject[key]])
        } else {
            addDirectives(key,{type:REMOVE,index:oldKeyIndexObject[key]})
        }
    }
    if(>0){
        addDirectives(parentKey,{type:MOVE, moved:movedItem})
    }
}

After passing the Diff method, we will get the comparison result of the oldNode and newNode we passed in and record it in the Directives object.

Patch

What Patch mainly does is to modify the Dom tree through the Directives object we obtained from our previous comparison. In the Patch method, if the node involves updates, the applyPatch method will be called.

export default function patch(node,directives){
    if(node){
        var orderList = []
        for(let child of ){
            patch(child,directives)
        }
        if(directives[]){
            applyPatch(node,directives[])
        }
    }
}

The applyPatch method mainly modifies specific Dom nodes.

Depending on the different types of directives, different methods are called for updates.

function applyPatch(node, directives){
    for(let directive of directives){
        switch (){
            case TEXT:
                setContent(node,)
                break
            case PROP:
                setProps(node,)
                break
            case REMOVE:
                removeNode(node)
                break
            case INSERT:
                insertNode(node,,)
            default:
                break
        }
    }
}

The specific update method is to operate the DOM node through js.

Complete code

The above is the detailed content of the simple implementation of Virtual DOM. For more information about the simple implementation of Virtual DOM, please pay attention to my other related articles!