This article is written natively. Simulate the DIff algorithm of render and DOM update in Vue.

  1. Creating a virtual DOM
  2. The render function converts the virtual DOM into a real DOM and renders it to the browser.
  3. By updating the old and new virtual DOM (diff algorithm).
  4. Update DOM rendering with patch.

What is the virtual DOM?

The Virtual DOM (Virtual DOM), also known as the Virtual node, is used to simulate nodes in the real DOM with JS objects. The object contains the structure and attributes of the real DOM and is used to compare the differences between the Virtual DOM and the real DOM, so as to achieve the purpose of optimizing performance by local rendering.

Implementing the virtual DOM

function createElement(type, props, ... children) {
    let key
    if (props.key) {
        key = props.key;
        delete props.key
    }
    // Process text nodes
    children = children.map(vnode= > {
        if (typeof vnode === "string") {
            return vNode(undefined.undefined.undefined, [], vnode)
        }
        return vnode
    })
    return vNode(type, props, key, children, text = undefined)}Copy the code
function vNode(type, props, key, children, text) {
    return { type, props, key, children, text }
}
Copy the code

Note that you don’t need to recursively handle children, because you’ll call the createElement method again in use. So you only need one layer.

After creating the virtual object, create the render function to render the virtual DOM into the real DOM.

function render(vNode, container) {
    // Pass in the virtual DOM and the real container;
    let ele = createDomElementFrom(vNode);
    container.appendChild(ele);
}		
Copy the code
function createDomElementFrom(vNode) { 
    // Return the virtual DOM to the real DOM
	let { type, props, key, children, text } = vNode;
    if (type) { // Check whether it is a label
        // Attach a real DOM attribute to the current virtual object;
        vNode.domElement = document.createElement(type);
        // Add attribute methods
        updateEleProperties(vNode);
        // Recursively invoke child components
        vNode.children.forEach(element= > {
        	render(element, vNode.domElement)
        });
    } else { // Text node
        vNode.domElement = document.createTextNode(text);
    }
	return vNode.domElement
}
Copy the code
function updateEleProperties(newVnode, oldPros = {}) {
    //oldPros pass this parameter in to update when you need to compare. We'll see later that the initial render is empty.
    let element = newVnode.domElement;
    let newProps = newVnode.props;

    // Events and other special attributes need to be handled by themselves
    for (let key in oldPros) { // First render will not go here
        // The new node does not have the attributes of the old node
        if(! newProps[key]) {delete element[key];
        }
        if (key === "style") {
            let oleStyleProps = oldPros.style || {};
            let a = newProps.style || {};
            for (let key in oleStyleProps) {
                // New style node does not have old style node attributes, delete directly
                if(! a[key]) { element.style[key] =' '; }}}}for (let key in newProps) {
        // Add attributes directly to the new node
        if (key === 'style') {
            //style special attribute
            let newStyleProps = newProps.style || {};
            for (let key in newStyleProps) {
                // Add the style attribute directly to the new nodeelement.style[key] = newStyleProps[key]; }}else{ element[key] = newProps[key]; }}}Copy the code

The basic initial render of the above render is done. It’s ready to run in a browser.

let oldVnode = createElement("div", { className: "xxx" }, 
                             createElement("span", {}, "I'm span hashtag.")); render(oldVnode,document.querySelector("#app"))
Copy the code

After the initial rendering of the virtual DOM is complete, it is time to update the DOM.

When the DOM is updated. You can’t just replace the old DOM with the new DOM. This is where the diff algorithm is needed to compare the difference between old and new nodes and what can be reused. Improve performance. Patch like VUE through patch function.

function patch(oldVnode, newVnode) { // patch old and new DOM update; patch
    if(oldVnode.type ! == newVnode.type) {// The new label type is inconsistent with the old label type.
        return oldVnode.domElement.parentNode.replaceChild(createDomElementFrom(newVnode), oldVnode.domElement);
    }

    if(oldVnode.text ! == newVnode.text) {// The text is inconsistent
        return oldVnode.domElement.textContent = newVnode.text;
    }

    // The attributes are inconsistent
    let domElement = newVnode.domElement = oldVnode.domElement;// Get the real DOM
    updateEleProperties(newVnode, oldVnode.props);// The second parameter is passed to compare the old props with the new one.

    // Compare three cases of child nodes
    if(! newVnode.children.length) {// newVnode has no child nodes
        domElement.innerHTML = ' ';
    } else if (oldVnode.children.length && newVnode.children.length) {
        // Both old and new have child nodes - enter the core diff comparison.
        updateChildren(domElement, oldVnode.children, newVnode.children);
    } else {
        //newVnode has no children and oldVnode has no children
        newVnode.children.forEach((children) = > {
            // Children is a virtual DOM that needs to be converted to the real DOM directly using the function conversion.
            domElement.appendChild(createDomElementFrom(children))
        })
    }
}
Copy the code

Core diff

Two queues are used to maintain the old and new child nodes, and the index pointing to the head and tail of the old and new child nodes and corresponding information are compared one by one. And it’s a peer comparison.

Code implementation

Here I give the idea of code implementation, mainly according to the dom update situation to make a judgment condition to update.

  1. Get the start and end indexes of the old and new child nodes and the corresponding index virtual objects.

  2. Through a while loop, you exit the while if the starting index of either of the old and new child nodes is greater than the ending index. Inside the while is to make some conditional judgments (based on the judgments between nodes). Change pointer movement and DOM updates.

    1. The old and new start Pointers move one to the right, leaving the end pointer unchanged. (New head corresponds to old head)

    1. The old and new end Pointers remain the same one to the left. (New tail corresponds to old tail)

    2. The new start pointer moves one to the right, leaving the end pointer unchanged. The old end pointer goes one to the left, the start pointer stays the same. (New head, old tail)

    3. The new end pointer goes one to the left, the start pointer stays the same. The old start pointer moves one to the right, leaving the end pointer unchanged. (New tail and old head)

    1. No rules (there are two cases)
      • Can reuse

        • Find the index corresponding to the key in the mapping table and add this node to the front of oldStartVnode.

      • unreusable

        • Add it directly to oldStartVnode.
  3. The condition for breaking out of the while loop is the shortest queue. What is left is to determine the extra length of the old and new child nodes. New ones that are longer will be added and vice versa.

function updateChildren(parent, oldChildren, newChildren) {
    let oldStartIndex = 0;
    let oldStartVnode = oldChildren[oldStartIndex];
    let oldEndIndex = oldChildren.length - 1;
    let oldEndVnode = oldChildren[oldEndIndex];

    let newStartIndex = 0;
    let newStartVnode = newChildren[newStartIndex];
    let newEndIndex = newChildren.length - 1;
    let newEndVnode = newChildren[newEndIndex];

    let keyIndexMap = createMapBykeyToIndex(oldChildren); // The key-index mapping table of the old node

    while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
        if (isSameNode(oldStartVnode, newStartVnode)) { 1 / / conditions
            patch(oldStartVnode, newStartVnode);// Patch - Update old and new props
            oldStartVnode = oldChildren[++oldStartIndex];
            newStartVnode = newChildren[++newStartIndex];
        } else if (isSameNode(oldEndVnode, newEndVnode)) { 2 / / conditions
            patch(oldEndVnode, newEndVnode);
            oldEndVnode = oldChildren[--oldEndIndex];
            newEndVnode = newChildren[--newEndIndex];
        } else if (isSameNode(oldStartVnode, newEndVnode)) { 3 / / conditions
            patch(oldStartVnode, newEndVnode);
            parent.insertBefore(oldStartVnode.domElement, oldEndVnode.domElement.nextSiblings);
            oldStartVnode = oldChildren[++oldStartIndex];
            newEndVnode = newChildren[--newEndIndex];
        } else if (isSameNode(oldEndVnode, newStartVnode)) { 4 / / conditions
            patch(oldEndVnode, newStartVnode);
            parent.insertBefore(oldEndVnode.domElement, oldStartVnode.domElement);
            oldEndVnode = oldChildren[--oldEndIndex];
            newStartVnode = newChildren[++newStartIndex];
        } else {  // No rule comparison condition 5
            let index = keyIndexMap[newStartVnode.key];
            if (index == null) {// No reuse
                // Convert the virtual DOM to the real DOM
                parent.insertBefore(createDomElementFrom(newStartVnode), oldStartVnode.domElement);
                newStartVnode = newChildren[++newStartIndex];
            } else {// There is reuse
                patch(oldChildren[index], newStartVnode);
                parent.insertBefore(oldChildren[index].domElement, oldStartVnode.domElement);
                newStartVnode = newChildren[++newStartIndex];
                oldChildren[index] = undefined; }}}if (newStartIndex <= newEndIndex) {
        // Same start node, extra end
        for (let index = newStartIndex; index <= newEndIndex; index++) {
            let beforeElement = newChildren[newEndIndex + 1]? newChildren[newEndIndex +1].domElement : null;
            // Null insertion equals appendChild converting the virtual DOM to the real DOMparent.insertBefore(createDomElementFrom(newChildren[index]), beforeElement); }}else if (oldStartIndex <= oldEndIndex) {
        // The old node is longer and the old node is retained - it needs to be deleted
        for (let index = oldStartIndex; index <= oldEndIndex; index++) {
            let element = oldChildren[index]
            if(element) { parent.removeChild(element.domElement); }}}}// Create a mapping table
function createMapBykeyToIndex(oldChildren) {
    let map = {};
    for (let index = 0; index < oldChildren.length; index++) {
        let element = oldChildren[index];
        if (element.key) {
            map[element.key] = index
        }
    }
    return map;
}
// Check whether the nodes are the same
function isSameNode(oleVnode, newVnode) {
    return oleVnode.type === newVnode.type && oleVnode.key === newVnode.key
}
Copy the code

Simple virtual DOM and DOM update when the application of diff algorithm has been basically completed. You can take the data and test it in the browser.

let oldVnode = createElement("div", { className: "xxx" },
                             createElement("li", { key: "A" }, "A"),
                             createElement("li", { key: "B" }, "B"),
                             createElement("li", { key: "C" }, "C"),
                             createElement("li", { key: "D" }, "D")); render(oldVnode,document.querySelector("#app"))
let newVnode = createElement("div", {},
                             createElement("li", { key: "A" }, "A"),
                             createElement("li", { key: "B" }, "B"),
                             createElement("li", { key: "C" }, "C"),
                             createElement("li", { key: "D".id: "ID" }, "D"),
                             createElement("li", { key: "E" }, "E"));setTimeout(() = > {
    patch(oldVnode, newVnode)
}, 2000); 
Copy the code

Test the other data in the format shown in the condition diagram. I’ve just done a little bit of this and it’s basically just a simulation.

The end

I write less technical articles, write an article to share, not only on their own learning technology summary can also improve their level of writing articles. Very good very good