Writing in the front

Vue2.0 adds the virtual dom, which is a bit like react. The diff of vue is located in patch.js file with complexity O(n). Understanding the diff process can help us use frames more efficiently, and make it easier to find a job and a girlfriend. To understand the diff process, let’s start with the virtual DOM.

Virtual dom

The so-called virtual DOM is a virtual node. It simulates the nodes in DOM through the Object of JS, and then renders them into real DOM nodes through the specific render method. DOM DIff returns a patch Object through the calculation at the JS level, namely the patch Object, and parses the patch Object through specific operations. Complete the re-rendering of the page, the previous image makes it a little clearer:

We can do an experiment. By printing out the first-level attributes of an empty element, you can see that the standard lets the element do too much. If you regenerate new elements every time, it’s a huge waste of performance.

var odiv = document.createElement('div');
for(var k in odiv ){
  console.log(k)
}
Copy the code

Look at your printer’s desk. It has the results you want.

Implementation steps

  • Emulate the DOM with JavaScript objects
  • Convert this virtual DOM into a real DOM and insert it into the page
  • If something happens to change the virtual DOM
  • The difference object is obtained by comparing the difference between two virtual DOM trees
  • Apply the difference object to a real DOM tree

Code implementation

class crtateElement {
    constructor (el, attr, child) {
        this.el = el
        this.attrs = attr
        this.child = child || []
    }
    render () { 
        letVirtualDOM = document.createElement(this.el) // attr is an object to iterate overfor (var attr inthis.attrs) { virtualDOM.setAttribute(attr, This.attrs [attr])} child this.child. ForEach (el => {console.log(el instanceof crtateElement)) Call its render method to create the actual DOM of the child node, or a file node if it is a stringlet childElement = (el instanceof crtateElement) ? el.render() : document.createTextNode(el);
            virtualDOM.appendChild(childElement);
        });
        return virtualDOM
    }
}
function element (el, attr, child) {
    return new crtateElement(el, attr, child)
}

module.exports = element
Copy the code

Using JavaScript object structure to represent the DOM tree structure; Then use this tree to build a real DOM tree and plug it into the document

let element = require('./element') 

let myobj = {
    "class": 'big_div'
}
let ul = element('div',myobj,[
    'I am the word',
    element('div', {'id': 'xiao'},'1']),
    element('div', {'id': 'xiao1'},'2']),
    element('div', {'id': 'xiao2'},'3']),
])
console.log(ul)
ul = ul.render()
document.body.appendChild(ul)
Copy the code

DOM DIFF

Comparing the difference between two DOM trees is the core part of Virtual DOM algorithm. In simple terms, the new virtual DOM is compared with the old one. If there is any difference, the new one will prevail, and then the real DOM will be inserted and re-rendered. , borrow a picture of the network:

Comparisons are made only at the same level, not across levels. After comparison, there will be four cases: 1, whether the node is removed -> add new node 2, whether the attribute is changed -> old attribute is changed to new attribute 3, text content is changed -> old content is changed to new content 4, node is replaced by the whole -> structure is completely different remove the whole replacement

Look at the simple code implementation of diff.js, which is explained below

let utils = require('./utils');

let keyIndex = 0;
functionDiff (oldTree, newTree) {// An empty object to record the difference. The key is the ordinal number of the old node in the original virtual DOM tree, and the value is an array of difference objectsletpatches = {}; keyIndex = 0; // The son needs another signletindex = 0; 1.2 Walk (oldTree, newTree, index, patches);returnpatches; } / / traversefunction walk(oldNode, newNode, index, patches) {
    letcurrentPatches = []; // All oldNode changes are recorded in this arrayif(! NewNode) {// if the newNode is not available, the node is considered to be deleted currentPatches. Push ({type: utils.REMOVE, index }); // If the new nodes of the old node are text nodes}else if(utils.isString(oldNode) && utils.isString(newNode)) {// If the new character value is different from the old oneif(oldNode ! = newNode) {/// Text changes currentPatches. Push ({type: utils.TEXT, content: newNode }); }}else if(oldNode.tagName == newNode.tagName) {// Compare the attribute objects of the old and new elementsletattrsPatch = diffAttr(oldNode.attrs, newNode.attrs); // If the old and new elements have different attributesif(object.keys (attrsPatch).length > 0) {// Add to the difference array currentPatches. Push ({type: utils.ATTRS, attrs: attrsPatch }); } // diffChildren(oldnode. children, newnode. children, index, patches, currentPatches); }else {
        currentPatches.push({ type: utils.REPLACE, node: newNode });
    }
    if(currentPatches.length > 0) { patches[index] = currentPatches; }} // Sons of the old node Sons of the new node serial number of the parent node complete patch object Patch object of the current old nodefunction diffChildren(oldChildren, newChildren, index, patches, currentPatches) {
    oldChildren.forEach((child, idx) => {
        walk(child, newChildren[idx], ++keyIndex, patches);
    });
}
function diffAttr(oldAttrs, newAttrs) {
    let attrsPatch = {};
    for (let attr inOldAttrs) {// If the old attribute is not the same as the new attribute. Either the value changes or the property is deletedif (oldAttrs[attr] != newAttrs[attr]) {
            attrsPatch[attr] = newAttrs[attr];
        }
    }
    for (let attr in newAttrs) {
        if (!oldAttrs.hasOwnProperty(attr)) {
            attrsPatch[attr] = newAttrs[attr];
        }
    }
    return attrsPatch;
}
module.exports = diff;
Copy the code

One thing to note is that when the old and new virtual DOM are compared, they are compared in the same layer first. When they are compared in the same layer, there are sons, and then they need to continue to compare until there are no sons. Here’s a simple graph to illustrate:

Simple implementation of patch.js

let keyIndex = 0;
let utils = require('./utils');
letallPatches; // Here is the complete patch packagefunction patch(root, patches) {
    allPatches = patches;
    walk(root);
}
function walk(node) {
    let currentPatches = allPatches[keyIndex++];
    (node.childNodes || []).forEach(child => walk(child));
    if (currentPatches) {
        doPatch(node, currentPatches); }}function doPatch(node, currentPatches) {
    currentPatches.forEach(patch => {
        switch (patch.type) {
            case utils.ATTRS:
                for (let attr in patch.attrs) {
                    let value = patch.attrs[attr];
                    if (value) {
                        utils.setAttr(node, attr, value);
                    } else{ node.removeAttribute(attr); }}break;
            case utils.TEXT:
                node.textContent = patch.content;
                break;
            case utils.REPLACE:
                let newNode = (patch.node instanceof Element) ? path.node.render() : document.createTextNode(path.node);
                node.parentNode.replaceChild(newNode, node);
                break;
            case utils.REMOVE:
                node.parentNode.removeChild(node);
                break; }}); } module.exports = patch;Copy the code

It means that you can fill it later. Welcome to leave a message. The diff section also has a key setting that makes more efficient use of the DOM. That’s also important. It’s getting late. Write another day. Your likes are my constant motivation. Thank you, welcome to throw brick ~~