If you will, I will love you forever

preface

Virtual DOM(Virtual DOM), more or less involved in the current three front-end frameworks, and in the front-end volume environment as a high-frequency test point of the interview, it is very necessary for us to uncover its mysterious veil, to explore.

Why do you need the virtual DOM

In virtual DOM, jquery fashionable whole world at that time, after we get to the data through ajax to operate DOM directly back to the browser to render, but in the disadvantages of a large number of data of operation when it is out, because a lot of frequent operation on the DOM, so slow page load or even caton, At the same time, jquery also leads to code coupling that makes projects difficult to maintain.

Why does frequent DOM manipulation cause browser performance problems? The reason for this is that the DOM in the browser is “expensive”. A single DOM element has a lot of properties attached to it, so we can get a sense of how big it can be:

var div = document.createElement('div')
var str = ' '
for (let key in div) {
  str += key + ' '
}
console.log(str)
Copy the code

So manipulating the DOM is very performance-intensive. Fortunately, the era of MVVM has brought with it a virtual DOM solution that essentially trades the computational performance of JS for the performance consumed by manipulating the DOM. A DOM node is simulated by JS, which is called virtual DOM node. When the data changed, we compared the virtual DOM nodes before and after the change, calculated the places to be updated through dom-Diff algorithm, and then updated the view to be updated. This approach allows us to minimize DOM manipulation, which improves performance. The emergence of the three frameworks, so that the code logic, organizational ability is stronger, but also reduce the difficulty of maintenance.

The implementation of virtual DOM

The virtual DOM is a DOM node represented by a native JS object, corresponding to a Vue, which is defined as a vnode class in SRC /core/vdom/vnode.js:

export default class VNode {
  constructor (tag? : string, data? : VNodeData, children? :?Array<VNode>, text? : string, elm? : Node, context? : Component, componentOptions? : VNodeComponentOptions, asyncFactory? :Function
  ) {
    this.tag = tag                                /* Label name of the current node */
    this.data = data        /* The object corresponding to the current node, which contains specific data information, is a VNodeData type, you can refer to the VNodeData type data information */
    this.children = children  /* The child of the current node is an array */
    this.text = text     /* The text of the current node */
    this.elm = elm       /* The actual DOM node corresponding to the current virtual node */
    this.ns = undefined            /* Namespace of the current node */
    this.context = context          /* Vue instance corresponding to the current component node */
    this.fnContext = undefined       /* The Vue instance of the functional component */
    this.fnOptions = undefined
    this.fnScopeId = undefined
    this.key = data && data.key           /* The key attribute of the node, which is used as the node's identifier, is used to optimize */
    this.componentOptions = componentOptions   /* The component's option */
    this.componentInstance = undefined       /* The instance of the component corresponding to the current node */
    this.parent = undefined           /* The parent of the current node */
    this.raw = false         InnerHTML is true, and textContent is false*/
    this.isStatic = false         /* Static node flag */
    this.isRootInsert = true      /* Whether to insert as the heel node */
    this.isComment = false             /* Is a comment node */
    this.isCloned = false           /* Whether the node is a clone */
    this.isOnce = false                /* Whether there is a v-once instruction */
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }

  get child (): Component | void {
    return this.componentInstance
  }
}
Copy the code

The VNode class contains a set of attributes needed to describe a real DOM node. The tag attribute represents the tag name of the node, and the data attribute represents the data object corresponding to the current node (the VNodeData type can be defined in the flow/vnode.js file). The children attribute represents the child virtual node. These three parameters correspond to the createElement parameters in the render function.

Using this class, we can instantiate different types of virtual DOM nodes to describe different types of real DOM nodes. VNode types are as follows:

  • Text node
// Create a text node
export function createTextVNode (val: string | number) {
  return new VNode(undefined.undefined.undefined.String(val)) 
  // Only the fourth parameter is needed
}
Copy the code
  • Comment node
// Create a comment node
export const createEmptyVNode = (text: string = ' ') = > {
  const node = new VNode()
  node.text = text // Comment out the contents of the node
  node.isComment = true // Identifies the key attributes of the annotation node
  return node
}
Copy the code
  • Clone node
// Create a clone node
export function cloneVNode (vnode: VNode) :VNode {
  // Copy the attributes of the passed node into a new instance object
  const cloned = newVNode( vnode.tag, vnode.data, vnode.children, vnode.text, vnode.elm, vnode.context, vnode.componentOptions, vnode.asyncFactory ) cloned.ns = vnode.ns cloned.isStatic = vnode.isStatic cloned.key = vnode.key cloned.isComment = vnode.isComment cloned.fnContext = vnode.fnContext cloned.fnOptions = vnode.fnOptions cloned.fnScopeId = vnode.fnScopeId  cloned.asyncMeta = vnode.asyncMeta cloned.isCloned =true // Identifies the key properties of the clone node
  return cloned
}
Copy the code
  • Element nodes

CreateElement (SRC /core/vdom/create-element.js);

  • Component node

In addition to the attributes that element nodes have, a component node has two unique attributes:

ComponentOptions: options for components, such as props. ComponentInstance: Vue instance corresponding to the current component node

See the createComponent function in SRC /core/vdom/create-component.js.

  • Functional component nodes

A functional component node has two unique properties compared to a component node:

FnContext: Vue instance corresponding to the functional component fnOptions: Option options of the component

As shown in the SRC/core/vdom/create – functional – component. The createFunctionalComponent function of js file.

  • Static node

Nodes whose isStatic property is true mark static nodes during the optimization phase of template compilation

DOM-Diff

We compare old and new VNodes to find differences and then do the least amount of DOM manipulation. This process of finding differences is implemented by dom-Diff algorithm, which is important.

Dom-diff is performed when the vm._update() function is called. Vm.__patch__ () is called internally, and vM.__patch__ eventually refers to the patch function returned by createPatchFunction, So we call the DOM-Diff process the patch process. Patch means “patch”, that is, patch the old VNode to get a new VNode, the name is too aptly named.

Simply speaking, the whole patch actually does three things:

  • Create a node: the new VNode has one and the old oldVNode does not. Create the node in the old oldVNode.
  • Delete node: if the new VNode does not exist and the old oldVNode does, it is deleted from the old oldVNode.
  • Update nodes: Both the new VNode and the old oldVNode exist. Update the old oldVNode using the new VNode.

Create a node

The VNode class can describe six types of nodes, but only three types of nodes can actually be created and inserted into the DOM: element nodes, text nodes, and comment nodes. These are the only three types of nodes that correspond to real DOM nodes. Take component node as an example, we create a component node through createComponent method. The granularity of VUe2.0 is medium component level, that is to say, the level of patch is component level. In essence, the process of component node patch is the comparison of element node, text node and annotation node.

Here’s a quick overview of component principles to help you understand:

Principle component

import Vue from 'vue'
import App from './App.vue'

var app = new Vue({
  el: '#app'.// h is the createElement method
  render: h= > h(App)
})
Copy the code

Vue single-file components are parsed by vue-loader, and all vue-loader does is compile the template and style in the. Vue file into js (compile into render function). And mix it into the Object that you exported in.vue. The essence of a component is a reusable vUE instance (an instance of a vUE subclass built by vue.extend).

The createElement function determines that the current tag is not a normal HTML tag and calls createComponent. Extend and install hook functions (init, prepatch, Insert, destroy) through installComponentHooks(). Finally, create a component VNode and return it. Then the execution of the vm. _update () is the patch stage, performs the init hook function, to obtain a previously built good Vue subclasses and instantiate the Vue subclass instantiation will perform the instance _init () function, and will compile, implement vm. _update (vm) and _render ()), This process is repeated internally when it comes to components, and it should be understood by now that components are essentially reusable VUE instances. Back to the original, the component finally calls the INSERT hook function after the whole patch is completed to complete the DOM insertion of the component. If a child component is created during the patch process, then the DOM insertion sequence is child before parent.

With the above overview, we can also easily understand the lifecycle order of vUE parent and child components when rendering:

Parent beforeCreate => parent created => parent beforeMount => child beforeCreate => child created => child beforeMount => child Mounted => parent Mounted

Summary: The principle of a component is to build a DOM tree through depth-first traversal of the entire nested component, and finally insert it into the real DOM once.

To continue with node creation, the following code has been simplified:

// SRC /core/vdom/patch.js
function createElm (vnode, parentElm, refElm) {
    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    if (isDef(tag)) {
      	vnode.elm = nodeOps.createElement(tag, vnode)   // Create the element node
        createChildren(vnode, children, insertedVnodeQueue) // Create a child node of the element node
        insert(parentElm, vnode.elm, refElm)       // Insert into the DOM
    } else if (isTrue(vnode.isComment)) {
      vnode.elm = nodeOps.createComment(vnode.text)  // Create a comment node
      insert(parentElm, vnode.elm, refElm)           // Insert into the DOM
    } else {
      vnode.elm = nodeOps.createTextNode(vnode.text)  // Create a text node
      insert(parentElm, vnode.elm, refElm)           // Insert into the DOM}}Copy the code
  • To determine whether it is an element node, you just need to determine thatVNodeWhether the node hastagLabels are ok. If you havetagAttribute is considered to be an element nodecreateElementMethod to create an element node, which usually has child nodes, throughcreateChildrenRecursive traversal creates all the child nodes, after all the child nodes have been createdinsertInsert into the current element node, and finally insert into the current element nodeDOMIn the.
  • To determine whether it is a comment node, you just need to determineVNodetheisCommentWhether the property istrueOk, if yestrueIs a comment node, and is calledcreateCommentMethod to create a comment node and insert it intoDOMIn the.
  • If it is neither an element node nor a comment node, it is considered a text node and is calledcreateTextNodeMethod creates a text node and inserts it intoDOMIn the.

Remove nodes

Deleting the node should be the easiest. If oldVNode does not exist in newVNode, remove the node using removeNode:

function removeNode (el) {
    const parent = nodeOps.parentNode(el)  // Get the parent node
    if (isDef(parent)) {
      nodeOps.removeChild(parent, el)  // Call the removeChild method on the parent node}}Copy the code

Update the node

The update node is the most complex, which is the most important. When certain nodes are present in both newVNode and oldVNode, we need to do a careful comparison to find different places to update.

  • newVNodewitholdVNodeAll are static nodes
<p> I am the text that will not change </p>Copy the code

This is a static node, without any mutable variables. Static nodes have nothing to do with any changes to their data, so they are skipped without processing.

  • newVNodeIs a text node (containing variables)
  1. oldVNodeAlso text nodes

To check whether two texts are the same, change the text in oldVNode to the text in newVNode.

  1. oldVNodeIs any node except the text node

Call setTextNode directly to change oldVNode to a text node and place the text content of newVNode.

  • newVNodeIs the element node
  1. newVNodeContain child nodes

Create a copy of newVNode’s child node and insert it into the oldVNode. Create a copy of newVNode’s child node and insert it into the oldVNode. Update child nodes by recursive comparison

2. NewVNode does not contain child nodes. If newVNode is an element node and does not contain child nodes, then newVNode is empty.

Update child nodes

As mentioned above, when both newVNode and oldVNode contain child nodes, the child nodes are updated by recursive comparison. How does this process work? Let’s look at it in detail.

First, let’s examine how vue determines that two nodes are the same:

Why is key recommended for list rendering

function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}
Copy the code

If the key is the same, then check the tag, isComment, data, etc. Consider a ul unordered list with three Li’s nested in it. If the key is not set or the key is set to index, then you add a li to the second VNode, and the old VNode and the old VNode are compared by sameVnode: The third LI of newVNode is the same as the third LI of oldVNode, and then updateChildren is executed to update the child node, where the child node may change a lot. Finally, a new LI is created and inserted at the end.

If key is set and key is not index, then it will determine: The third LI of newVNode cannot be found in oldVNode, and the fourth LI of newVNode is the same as the third LI of oldVNode. The child nodes here are generally unchanged or have small changes (in this case, we just added a new node). Finally, create a new LI and move it to the specified location. In this way, updating vNodes is more efficient. You can also think about deleting nodes.

Summary: Key is used to update the virtual DOM more efficiently.

Continuing, the children property on a VNode instance is an array of contained child nodes. We call the array of children on newVNode newChildren and the array of children on oldVNode oldChildren. Now we need to compare the elements in newChildren with the elements in oldChildren. To compare two arrays of child nodes, loop through the outer array newChildren (based on the new) and the inner array oldChildren (based on the oldChildren). For each child in the outer array newChildren, loop through the inner array oldChildren to see if it has the same child. This process will have the following four situations:

  • Creating child Nodes

If a child node in newChildren cannot find the same child node in oldChildren, it means that the child node in newChildren does not exist before and needs to be added this time. Then we create this node and insert it into the DOM at the appropriate position after creation. This position is before all unprocessed nodes.

  • Deleting child Nodes

If we loop through each child node in newChildren, we will process it if we can find it in oldChildren array, and we will add it if we can’t find it, until we have passed through all the children in oldChildren and found that there are still unprocessed child nodes in oldChildren. This indicates that these unprocessed child nodes need to be discarded, and these nodes need to be deleted.

  • Update child nodes

If one of the children in newChildren finds the same child in oldChildren in the same location, then the node in oldChildren is updated to be the same as the node in newChildren. Nested child nodes are recursively updated.

  • Move child node

If a child node in newChildren finds the same child node in oldChildren but in a different position, it indicates that the change needs to adjust the position of the child node, then the position of the child node in newChildren is taken as the baseline. Adjust the position of the node in oldChildren to be the same as in newChildren.

Optimization strategy

The double-layer loop can solve the problem, but if there are many nodes, the time complexity of the loop algorithm will increase exponentially. Vue has also realized this, so it is optimized here.

In simple terms, the optimization strategy is to try the following four situations during the cycle comparison:

  • The new and the old
  • Contrast the new with the old
  • Contrast the new with the old
  • Before the new and after the old

NewChildren: the last child of all unprocessed children in the array oldChildren: the first child of all unprocessed children in the array oldChildren: The last child of all unprocessed children in the oldChildren array

These four attempts can largely avoid extreme cases, reduce the number of cycles and improve the update efficiency. If the last four cases are not the same, then the previous loop is used to find the update node.

conclusion

This article is more inclined to notes, summary, and added their own understanding of learning, we come together ah!!

Reference: Vue source code series -Vue Chinese community Vue. Js technology revealed