[Vue2.0 source code learning] Virtual DOM articles - DOM-Diff in Vue

1 Introduction

In the introduction of the previous article VNode, we said that VNodethe biggest use is to generate real DOMcorresponding virtual DOMnodes before and after data changes, and then compare the old and new copies VNodeto find out the differences, and then update the nodes with differences DOM, and finally achieve the following Minimal manipulation DOMof the purpose of actually updating the view. And VNodethe process of comparing the old and new copies and finding the differences is the so-called DOM-Diffprocess. DOM-DiffAlgorithms are DOMthe core of the entire virtual world, so next, we will start with the source code and study the process Vuein depth DOM-Diff.

2. patch

In Vue, DOM-Diffthe process is called patchthe process. Patch, which means "patch", refers to repairing the old VNode, patching to get a new one VNode, very vivid. No matter what it is called, its essence is the process of comparing the old and new copies VNode. When we study patchthe process below, we must grasp such an idea: the so-called old VNode(that is oldVNode) is the virtual node corresponding to the view before the data change DOM, and the new one is the virtual node VNodecorresponding to the new view to be rendered after the data change , so we have to compare the old one with the new DOMone generated , if there are nodes on the new one but not on the old one, then add them to the old one ; if there are no nodes on the new one but there are nodes on the old one , then delete it on the old one; if some nodes exist on both the new one and the old one , then the new one prevails and the old one is updated so that the old and new are the same.VNodeoldVNodeVNodeoldVNodeoldVNodeVNodeoldVNodeoldVNodeVNodeoldVNodeVNodeoldVNodeVNode

Maybe you feel a little confused, it doesn’t matter, we are talking about it in a more general way, you can understand it this way: suppose you have an old electronic document on your computer, and the boss gives you a new paper document at this time, and Tell you that most of the contents of these two documents are the same, let you take the new paper version as the standard, and make a new electronic version of the paper version and send it to the boss. For this task at this time, you should have two solutions: one solution is to delete all the content of the old document, and then type in the new paper document word by word, This kind of solution doesn't need to bother your brain, even if you are a little tired, you can solve the problem. The other solution is to use the new paper version as the benchmark to compare the old electronic version with the new paper version. If some parts are in the new document but not in the old document , then add these parts in the old document; if some parts are not in the new document but in the old document, then delete these parts in the old document; if some parts are in the new and old documents There are all in the document, then compare to see if there is anything that needs to be updated, and finally update it in the old document, and finally achieve the perfect solution of turning the old document into the same as the paper version in hand.

Comparing the above two options, it is obvious that you are as Vuesmart as and will definitely choose the second option. In the second solution, the old electronic version of the document corresponds to the one that has been rendered on the view oldVNode, and the new paper version of the document corresponds to the new one that will be rendered on the view VNode. In a word: Based on the new VNode, transform the old oldVNode to make it the same as the new VNode. This is what the patch process should do .

Having said so much, it sounds very complicated, but it is not. If we think about it carefully, the whole thing patchis nothing more than three things:

  • Create a node: VNodeIf there is one in the new one but oldVNodenot in the old one, it will be created in the old one oldVNode.
  • Delete node: If there VNodeis no node in the new one but there is one in oldVNodethe old one, delete it from the old one oldVNode.
  • Update node: both new VNodeand old , the new one prevails and the old one is updated .oldVNodeVNodeoldVNode

OK, at this point, you have half understood the process Vueof middleware . Next, we will analyze one by one to see how to do the above three things.patchVue

3. Create a node

In the previous article, we analyzed that VNodea class can describe 6 types of nodes, but in fact only 3 types of nodes can be created and inserted into it DOM, they are: element nodes, text nodes, and comment nodes. Therefore, Vuewhen creating a node, it will be judged which type of node the node is in the new one VNodebut not in the old one , and then call different methods to create and insert it into the node.oldVNodeDOM

In fact, it is not difficult to judge, because the characteristics of these three types of nodes are very obvious, how to judge in the source code:

// 源码位置: /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)   // 创建元素节点
      createChildren(vnode, children, insertedVnodeQueue) // 创建元素节点的子节点
      insert(parentElm, vnode.elm, refElm)       // 插入到DOM中
    } else if (isTrue(vnode.isComment)) {
    
    
      vnode.elm = nodeOps.createComment(vnode.text)  // 创建注释节点
      insert(parentElm, vnode.elm, refElm)           // 插入到DOM中
    } else {
    
    
      vnode.elm = nodeOps.createTextNode(vnode.text)  // 创建文本节点
      insert(parentElm, vnode.elm, refElm)           // 插入到DOM中
    }
  }

From the above code, we can see that:

  • To judge whether it is an element node, you only need to judge VNodewhether the node has taga label. If there is tagan attribute, it is regarded as an element node, and the createElementmethod is called to create the element node. Usually, the element node will also have child nodes, then recursively traverse to create all child nodes, insert all child nodes insertinto the current element node after creation, and finally put The current element node is inserted into DOM.
  • To judge whether it is a comment node, you only need to judge VNodewhether isCommentthe attribute is true, if it is true, it is a comment node, then call createCommentthe method to create a comment node, and then insert it into DOM.
  • If it is neither an element node nor a comment node, it is regarded as a text node, and the method is called createTextNodeto create a text node, and then inserted into it DOM.

In the code, all node operations nodeOpsare Vueencapsulated for cross-platform compatibility. For example, nodeOps.createTextNode()on the browser side, it is equivalent todocument.createTextNode()

The above completes the operation of creating a node, and its complete flow chart is as follows:insert image description here

4. Delete node

If some nodes are not in the new one VNodebut in the old one oldVNode, then these nodes need to be oldVNodedeleted from the old one. Removing a node is as simple as calling a method on the parent element of the node to be removed removeChild. The source code is as follows:

function removeNode (el) {
    
    
  const parent = nodeOps.parentNode(el)  // 获取父节点
  if (isDef(parent)) {
    
    
    nodeOps.removeChild(parent, el)  // 调用父节点的removeChild方法
  }
}

5. Update the node

It is relatively simple to create and delete nodes, but it is relatively more complicated to update nodes. In fact, it is not too complicated, as long as the logic is cleared up, it can be understood.

Updating nodes is when some nodes are in both the new VNodeand the old oldVNode, we need to compare them carefully to find out the difference and update them.

Before introducing the update node, let's introduce a small concept, that is, what is a static node? Let's look at an example:

<p>我是不会变化的文字</p>

The above node only contains plain text, without any variable variables. That is to say, no matter how the data changes, as long as the node is rendered for the first time, it will never change in the future. This is because It contains no variables, so any changes in the data are irrelevant to it. We call this kind of node a static node.

OK, with this concept in mind, we start updating nodes. When updating a node, we need to judge and deal with the following three situations:

  1. If VNodeboth and oldVNodeare static nodes

    As we said, no matter what changes in the data of static nodes, it has nothing to do with it, so if they are all static nodes, they can be skipped directly without processing.

  2. If VNodeit is a text node

    If VNodeit is a text node, it means that this node only contains plain text, then just check oldVNodewhether it is also a text node, if so, then compare whether the two texts are different, and if they are different, oldVNodechange the text in it to be the same as the text in it VNode. If oldVNodeit is not a text node, no matter what it is, directly call setTextNodethe method to change it into a text node, and the text content is VNodethe same as that of the text node.

  3. if VNodeelement node

    If VNodeit is an element node, the following two cases are subdivided:

    • This node contains child nodes

      If the new node contains child nodes, then it depends on whether the old node contains child nodes. If the old node also contains child nodes, it needs to recursively compare and update the child nodes; if the old node does not contain Child node, then the old node may be an empty node or a text node. If the old node is an empty node, create a copy of the child node in the new node and insert it into the old node. If the old node is a text node node, clear the text, then create a copy of the child nodes in the new node and insert it into the old node.

    • This node contains no child nodes

      If the node does not contain child nodes and it is not a text node, it means that the node is an empty node, so it is easy to handle, no matter what was in the old node before, just empty it.

OK, after dealing with the above three situations, the update node is basically completed. Next, let’s see how it is implemented in the source code. The source code is as follows:

// 更新节点
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    
    
  // vnode与oldVnode是否完全一样?若是,退出程序
  if (oldVnode === vnode) {
    
    
    return
  }
  const elm = vnode.elm = oldVnode.elm

  // vnode与oldVnode是否都是静态节点?若是,退出程序
  if (isTrue(vnode.isStatic) &&
    isTrue(oldVnode.isStatic) &&
    vnode.key === oldVnode.key &&
    (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
  ) {
    
    
    return
  }

  const oldCh = oldVnode.children
  const ch = vnode.children
  // vnode有text属性?若没有:
  if (isUndef(vnode.text)) {
    
    
    // vnode的子节点与oldVnode的子节点是否都存在?
    if (isDef(oldCh) && isDef(ch)) {
    
    
      // 若都存在,判断子节点是否相同,不同则更新子节点
      if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
    }
    // 若只有vnode的子节点存在
    else if (isDef(ch)) {
    
    
      /**
       * 判断oldVnode是否有文本?
       * 若没有,则把vnode的子节点添加到真实DOM中
       * 若有,则清空Dom中的文本,再把vnode的子节点添加到真实DOM中
       */
      if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
    }
    // 若只有oldnode的子节点存在
    else if (isDef(oldCh)) {
    
    
      // 清空DOM中的子节点
      removeVnodes(elm, oldCh, 0, oldCh.length - 1)
    }
    // 若vnode和oldnode都没有子节点,但是oldnode中有文本
    else if (isDef(oldVnode.text)) {
    
    
      // 清空oldnode文本
      nodeOps.setTextContent(elm, '')
    }
    // 上面两个判断一句话概括就是,如果vnode中既没有text,也没有子节点,那么对应的oldnode中有什么就清空什么
  }
  // 若有,vnode的text属性与oldVnode的text属性是否相同?
  else if (oldVnode.text !== vnode.text) {
    
    
    // 若不相同:则用vnode的text替换真实DOM的文本
    nodeOps.setTextContent(elm, vnode.text)
  }
}

The comments in the above code have been written very clearly. Next, we draw a flow chart to sort out the whole process. The flow chart is as follows:insert image description here

By comparing the flowchart and code, I believe you can easily understand the logic of updating nodes.

In addition, you may have noticed that if VNodeboth the old and the new contain child nodes, then the method for updating the child nodes is called in the code updateChildren, and we will learn the logic of this method in the next article.

6. Summary

In this article we introduce the algorithm Vuein DOM-Diff: the patch process. We first introduced the entire thought process of the algorithm, and then by sorting out the algorithm idea, we learned that the whole patchprocess does three things, namely: create nodes, delete nodes, and update nodes. And each thing has been carefully studied against the source code, and its logic flow chart has been drawn. In addition, for updating nodes, if both the new VNodeand old nodes contain child nodes, we need to update the child nodes in detail. We will learn about the process of updating child nodes in the next article.

Guess you like

Origin blog.csdn.net/weixin_46862327/article/details/130798945