Parse Vue 2.5's Diff algorithm and vue2.5diff Algorithm

Source: Internet
Author: User

Parse Vue 2.5's Diff algorithm and vue2.5diff Algorithm

DOM is "inherently slow", so all the front-end frameworks provide a way to optimize DOM operations. In Angular, dirty checking is used. React first proposes Virtual Dom, vue2.0 also adds Virtual Dom, similar to React.

This article will analyze the Virtual Dom used in Vue 2.5.3.

UpdataChildren is the core of the Diff algorithm. Therefore, this article analyzes the updataChildren text.

1. VNode object

A VNode instance contains the following attributes. The code is in src/core/vdom/vnode. js.

export default class VNode { tag: string | void; data: VNodeData | void; children: ?Array<VNode>; text: string | void; elm: Node | void; ns: string | void; context: Component | void; // rendered in this component's scope key: string | number | void; componentOptions: VNodeComponentOptions | void; componentInstance: Component | void; // component instance parent: VNode | void; // component placeholder node // strictly internal raw: boolean; // contains raw HTML? (server only) isStatic: boolean; // hoisted static node isRootInsert: boolean; // necessary for enter transition check isComment: boolean; // empty comment placeholder? isCloned: boolean; // is a cloned node? isOnce: boolean; // is a v-once node? asyncFactory: Function | void; // async component factory function asyncMeta: Object | void; isAsyncPlaceholder: boolean; ssrContext: Object | void; functionalContext: Component | void; // real context vm for functional nodes functionalOptions: ?ComponentOptions; // for SSR caching functionalScopeId: ?string; // functioanl scope id support
  • Tag: The tag Name of the current node.
  • Data: The data object of the current node. For specific fields, see VNodeData definition in vue source code types/vnode. d. ts.
  • Children: array type, including the Child Nodes of the current node
  • Text: the text of the current node. This attribute is generally available for text nodes or comment nodes.
  • Elm: The real dom node corresponding to the current virtual node
  • Ns: node namespace
  • Context: compilation Scope
  • FunctionalContext: function component Scope
  • Key: The key attribute of a node. It is used as the node identifier and is conducive to patch optimization.
  • ComponentOptions: options used to create a component instance
  • Child: component instance corresponding to the current node
  • Parent: The placeholder node of the component.
  • Raw: raw html
  • IsStatic: static node ID
  • IsRootInsert: whether to be inserted as the root node.
  • IsComment: whether the current node is a comment Node
  • IsCloned: whether the current node is a clone Node
  • IsOnce: whether the current node has a v-once Command

2. VNode Classification

VNode can be understood as a base class of VueVirtual Dom. The VNnode instances generated by the VNode constructor can be of the following types:

  • EmptyVNode: Comment node with no content
  • TextVNode: text node
  • ElementVNode: Common Element Node
  • ComponentVNode: component Node
  • CloneVNode: Clone node, which can be any of the above types. The only difference is that the isCloned attribute is true.

3. Create-Element source code parsing

This part of the code is in src/core/vdom/create-element.js, And I am directly sticking the Code with my comments

Export function createElement (context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode {// if (Array. isArray (data) | isPrimitive (data )) {normalizationType = children = data = undefined} // if alwaysNormalize is true, normalizationType should be set to the constant ALWAYS_NORMALIZE value if (isTrue (alwaysNormalize) {normalizationType = AL WAYS_NORMALIZE} // call _ createElement to create the virtual node return _ createElement (context, tag, data, children, normalizationType)} export function _ createElement (context: Component, tag?: String | Class <Component> | Function | Object, data?: VNodeData, children?: Any, normalizationType?: Number): VNode {/*** if data exists. _ ob __, indicating that data is observed by the Observer * data cannot be used as a virtual node * A warning should be thrown, and return an empty node ** the monitored data cannot be used as the data for vnode rendering. The reason is: * data may be changed during vnode rendering, which triggers monitoring, result in an unexpected operation */if (isDef (data) & isDef (data: any ). _ ob _) {process. env. NODE_ENV! = 'Production '& warn ('avoid using observed data object as vnode data: $ {JSON. stringify (data)} \ n' + 'always create fresh vnode data objects in each render! ', Context) return createEmptyVNode ()} // object syntax in v-bind if (isDef (data) & isDef (data. is) {tag = data. is} if (! Tag) {// when the is attribute of a component is set to a falsy value // Vue will not know what to render this component. // Therefore, an empty node is rendered. // in case component: is set to falsy value return createEmptyVNode ()} // key is a non-original value warning // warn against non-primitive key if (process. env. NODE_ENV! = 'Production '& isDef (data) & isDef (data. key )&&! IsPrimitive (data. key) {warn ('avoid using non-primitive value as key, '+ 'use string/number value instead. ', context)} // scope slot // support single function children as default scoped slot if (Array. isArray (children) & typeof children [0] === 'function') {data = data | {} data. scopedSlots = {default: children [0]} children. length = 0} // select different processing methods based on the value of normalizationType. if (normalizationType = ALWAYS_NORMALIZE) {children = normalizeChildren (children )} else if (normalizationType === SIMPLE_NORMALIZE) {children = simpleNormalizeChildren (children)} let vnode, ns // if the tag name is of the string type if (typeof tag = 'string ') {let Ctor // obtain the tag namespace ns = (context. $ vnode & context. $ vnode. ns) | config. getTagNamespace (tag) // if the tag is retained, if (config. isReservedTag (tag) {// platform built-in elements // create such a vnode = new VNode (config. parsePlatformTagName (tag), data, children, undefined, undefined, context) // if it is not a reserved word tag, try to find the definition of this label} else if (isDef (Ctor = resolveAsset (context. $ options, 'components', tag) {// component // if found, create a virtual component node vnode = createComponent (Ctor, data, context, children, tag )} else {// unknown or unlisted namespaced elements // check at runtime because it may get assigned a namespace when its // parent normalizes children // bottom scheme, create a normal vnode = new VNode (tag, data, children, undefined, undefined, context)} else {// when the tag is not a string, we think that tag is the component construction class // so we directly create // direct component options/constructor vnode = createComponent (tag, data, context, children)} if (isDef (vnode )) {// application namespace if (ns) applyNS (vnode, ns) return vnode} else {// return a blank node return createEmptyVNode ()} function applyNS (vnode, ns, force) {vnode. ns = ns if (vnode. tag = 'foreignobject ') {// use default namespace inside foreignObject ns = undefined force = true} if (isDef (vnode. children) {for (let I = 0, l = vnode. children. length; I <l; I ++) {const child = vnode. children [I] if (isDef (child. tag) & (isUndef (child. ns) | isTrue (force) {applyNS (child, ns, force )}}}}

4. Patch Principle

The patch function is defined in src/core/vdom/patch. js. The patch logic is simple and the code is not stuck.

The patch function receives six parameters:

  • OldVnode: The old virtual node or the old real dom Node
  • Vnode: new virtual node
  • Hydrating: whether to mix it with the real dom
  • RemoveOnly: special flag
  • ParentElm: parent node
  • RefElm: The new node will be inserted before refElm.

The patch logic is:

If the vnode does not exist but the oldVnode exists, it indicates the intention is to destroy the old node, then invokeDestroyHook (oldVnode) is called for marketing.

If the oldVnode does not exist but the vnode exists, it indicates that the intention is to create a new node, then createElm is called to create a new node.

Else when both vnode and oldVnode exist

If the oldVnode and vnode are the same node, the patchVnode is called for the patch.

When vnode and oldVnode are not the same node, if oldVnode is a real dom node or hydrating is set to true, you need to use the hydrate function to map the virtual dom to the real dom, set oldVnode to the corresponding virtual dom and find oldVnode. creates a real dom Node Based on the vnode and inserts it into the parent node oldVnode. location of elm

The logic of patchVnode is:

1. If the oldVnode and vnode are completely consistent, you do not need to do anything.

2. if the oldVnode and vnode are both static nodes with the same key, when the vnode is a clone node or a node controlled by the v-once Command, you only need to set the oldVnode. elm and oldVnode. all the child data is copied to the vnode, and no other operations are required.

3. Otherwise, if the vnode is not a text node or comment Node

  • If both the oldVnode and vnode have sub-nodes, and the sub-nodes on both sides are inconsistent, run updateChildren.
  • If only oldvnodes have subnodes, delete these nodes.
  • If only vnodes have subnodes, create these subnodes.
  • If neither oldVnode nor vnode has a subnode, but oldVnode is a text node or comment node, set vnode. elm text to a null string.

4. If vnode is a text node or comment node, but vnode. text! = OldVnode. text, you only need to update the text content of vnode. elm.

The Code is as follows:

Function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {// if the old and new nodes are consistent, nothing else if (oldVnode = vnode) {return} // Let vnode. el references the current real dom. When el is modified, vnode. el will synchronize the change const elm = vnode. elm = oldVnode. elm // asynchronous placeholder if (isTrue (oldVnode. isAsyncPlaceholder) {if (isDef (vnode. asyncFactory. resolved) {hydrate (oldVnode. elm, vnode, insertedVnodeQueue)} else {vnode. isAsyncPlaceholder = true} return} // Reuse element for static trees. // note we only do this if the vnode is cloned-// if the new node is not cloned it means the render functions have been // reset by the hot-reload-api and we need to do a proper re-render. // if both the old and new nodes are static nodes with the same key // when the vnode is a clone node or a node controlled by the v-once Command, you only need to set the oldVnode. elm and oldVnode. if (isTrue (vnode. isStatic) & isTrue (oldVnode. isStatic) & vno De. key = oldVnode. key & (isTrue (vnode. isCloned) | isTrue (vnode. isOnce) {vnode. componentInstance = oldVnode. componentInstance return} let I const data = vnode. data if (isDef (data) & isDef (I = data. hook) & isDef (I = I. prepatch) {I (oldVnode, vnode)} const oldCh = oldVnode. children const ch = vnode. children if (isDef (data) & isPatchable (vnode) {for (I = 0; I <cbs. update. length; ++ I) Cbs. update [I] (oldVnode, vnode) if (isDef (I = data. hook) & isDef (I = I. update) I (oldVnode, vnode)} // if the vnode is not a text node or if (isUndef (vnode. text) {// and all subnodes have if (isDef (oldCh) & isDef (ch) {// and the subnodes are inconsistent, updateChildren if (oldCh! = Ch) updateChildren (elm, oldCh, ch, insertedVnodeQueue, removeOnly) // if only new vnodes have subnodes} else if (isDef (ch )) {if (isDef (oldVnode. text) nodeOps. setTextContent (elm, '') // elm has referenced the old dom node and added the subnode addVnodes (elm, null, ch, 0, ch) to the old dom node. length-1, insertedVnodeQueue) // if the new vnode does not have a subnode and the vnode has a subnode, delete the old oldCh} else if (isDef (oldCh) {removeVnodes (elm, oldCh, 0, oldCh. length-1) // if the old node is a text node} else If (isDef (oldVnode. text) {nodeOps. setTextContent (elm, '')} // if the new vnode and the old vnode are text nodes or comment nodes // But vnode. text! = OldVnode. text, you only need to update the text content of vnode. elm.} else if (oldVnode. text! = Vnode. text) {nodeOps. setTextContent (elm, vnode. text)} if (isDef (data) {if (isDef (I = data. hook) & isDef (I = I. postpatch) I (oldVnode, vnode )}}

5. Principles of updatachil.pdf

The logic of updateChildren is:

Obtain firstChild and lastChild of oldVnode and vnode respectively, and assign them to oldStartVnode, oldEndVnode, newStartVnode, and newEndVnode.

If oldStartVnode and newStartVnode are the same node, call patchVnode to perform the patch, and set both oldStartVnode and newStartVnode to the next subnode,

If oldEndVnode and newEndVnode are the same node, call patchVnode for patch, and set oldEndVnode and newEndVnode to the previous subnode. Repeat the above process.

If oldStartVnode and newEndVnode are the same node, call patchVnode for patch. If removeOnly is false, you can set oldStartVnode. move elm to oldEndVnode. after elm, set oldStartVnode to the next node and newEndVnode to the previous node. Repeat the above process.

If newStartVnode and oldEndVnode are the same node, call patchVnode for patch. If removeOnly is false, you can set oldEndVnode. move elm to oldStartVnode. before elm, set newStartVnode to the next node and oldEndVnode to the previous node. Repeat the above process.

If none of the above matches, find the node with the same key as newStartVnode in oldChildren. If the node with the same key cannot be found, newStartVnode is a new node and one is created, set newStartVnode to the next node.

If the node with the same key as newStartVnode is found in the previous step, compare other attributes to determine whether the two nodes are the same node. If yes, call patchVnode for patch, if removeOnly is false, set newStartVnode. insert elm to oldStartVnode. before elm, set newStartVnode as the next node and repeat the above process.

If the same node of newStartVnode is not found in oldChildren, create a new node, set newStartVnode to the next node, and repeat the above process.

If oldStartVnode and oldEndVnode overlap, and newStartVnode and newEndVnode overlap, the loop will end.

The Code is as follows:

Function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {let oldStartIdx = 0 // old header index let newStartIdx = 0 // new header index let oldEndIdx = oldCh. length-1 // Old Tail Index let newEndIdx = newCh. length-1 // new tail index let oldStartVnode = oldCh [0] // oldVnode's first child let oldEndVnode = oldCh [oldEndIdx] // oldVnode's last child let newStartVnode = newCh [0] // newVnode's first child let newEndVnode = newC H [newEndIdx] // the last child let oldKeyToIdx, idxInOld, vnodeToMove, refElm // removeOnly is a special flag used only by <transition-group> // to ensure removed elements stay in correct relative positions // during leaving transitions const canMove =! RemoveOnly // If the oldStartVnode and oldEndVnode overlap and the new node also overlap, it means that the diff is finished and the loop ends while (oldStartIdx <= oldEndIdx & newStartIdx <= newEndIdx) {// if the first child of the oldVnode does not exist if (isUndef (oldStartVnode )) {// oldStart index right shift oldStartVnode = oldCh [++ oldStartIdx] // Vnode has been moved left // if the last child of the oldVnode does not exist} else if (isUndef (oldEndVnode )) {// oldEnd index left shift oldEndVnode = oldCh [-- oldEndIdx] // oldStartVnode and newSta RtVnode is the same node} else if (sameVnode (oldStartVnode, newStartVnode) {// patch oldStartVnode and newStartVnode, index left shift, continue loop patchVnode (oldStartVnode, newStartVnode, worker) oldStartVnode = oldCh [++ oldStartIdx] newStartVnode = newCh [++ newStartIdx] // oldEndVnode and newEndVnode are the same node} else (sameVnode (oldEndVnode, newEndVnode )) {// patch oldEndVnode and newEndVnode. The index is shifted to the right and patchVnode (oldEndVnode, NewEndVnode, callback) oldEndVnode = oldCh [-- oldEndIdx] newEndVnode = newCh [-- newEndIdx] // The same node as newEndVnode} else if (sameVnode (oldStartVnode, newEndVnode )) {// Vnode moved right // patch oldStartVnode and newEndVnode patchVnode (oldStartVnode, newEndVnode, insertedVnodeQueue) // If removeOnly is false, oldStartVnode is set. move eml to oldEndVnode. canMove & nodeOps. insertBefore (pare NtElm, oldStartVnode. elm, nodeOps. nextSibling (oldEndVnode. elm) // shifts the right of the oldStart index, newEnd index left shift oldStartVnode = oldCh [++ region] newEndVnode = newCh [-- newEndIdx] // if oldEndVnode and newStartVnode are the same node} else if (sameVnode (oldEndVnode, newStartVnode )) {// Vnode moved left // patch oldEndVnode and newStartVnode patchVnode (oldEndVnode, newStartVnode, insertedVnodeQueue) // If removeOnly is false, oldEndVnode is set. Move elm to oldStartVnode. canMove & nodeOps before elm. insertBefore (parentElm, oldEndVnode. elm, oldStartVnode. elm) // shifts the oldEnd index left, newStart index right shift oldEndVnode = oldCh [-- oldEndIdx] newStartVnode = newCh [++ newStartIdx] // if none match} else {if (isUndef (delimiter) Then = createKeyToOldIdx (oldCh, oldStartIdx, oldEndIdx) // try to find Vnode idxInOld = isDef (newStartVnode. key )? OldKeyToIdx [newStartVnode. key]: findIdxInOld (newStartVnode, oldCh, oldStartIdx, oldEndIdx) // if not found, newStartVnode is a new node if (isUndef (idxInOld )) {// New element // create a New Vnode createElm (newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode. elm) // if a Vnode with the same key as newStartVnodej is found, it is called vnodeToMove} else {vnodeToMove = oldCh [idxInOld]/* istanbul ignore if */if (process. env. NODE_ENV! = 'Production '&&! VnodeToMove) {warn ('It seems there are duplicate keys that is causing an update error. '+' Make sure each v-for item has a unique key. ')} // compare whether two new nodes with the same key are the same node. // if there is no key, newCh and oldCh only compare the start and end ends. After the key is set, in addition to the comparison between the beginning and end, it also finds matching nodes from the oldKeyToIdx object generated with the key, so setting the key for the node can make more efficient use of the dom. If (sameVnode (primary, newStartVnode) {// patch vnodeToMove and newStartVnode patchVnode (primary, newStartVnode, primary) // clear oldCh [idxInOld] = undefined // if removeOnly is false, the Vnode with the same key as newStartVnodej is called vnodeToMove. elm // move to oldStartVnode. canMove & nodeOps before elm. insertBefore (parentElm, vnodeToMove. elm, oldStartVnode. elm) // if the key is the same but the nodes are different, a new node is created} else {// same key but different element. treat as new element createElm (newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode. elm) }}// shift to the right newStartVnode = newCh [++ newStartIdx]}

6. Specific Diff Analysis

If no key is set, newCh and oldCh only compare the two ends of the header and end. After the key is set, except the comparison between the two ends of the header and end, it also searches for matched nodes from the oldKeyToIdx object generated with the key, so setting the key for the node can make more efficient use of the dom.

During diff traversal, as long as all operations on dom call api. insertBefore, api. insertBefore is a simple encapsulation of native insertBefore.

There are two types of comparison: vnode. key and none. However, these two methods are consistent with the actual dom operations.

When sameVnode (oldStartVnode, newStartVnode) and sameVnode (oldEndVnode, newEndVnode) are true, dom does not need to be moved.

There are three dom operations in the summary traversal process:

1. When oldStartVnode and newEndVnode are compared, it indicates that oldStartVnode. el is behind oldEndVnode. el.

2. When oldEndVnode and newStartVnode are worth comparing, oldEndVnode. el runs to the front of oldStartVnode. el. To be precise, oldEndVnode. el needs to be moved to the front of oldStartVnode. el ".

3. the node oldCh in newCh does not exist. Insert the new node to the front of oldStartVnode. el.

There are two cases at the end:

1. oldStartIdx> oldEndIdx, you can think that oldCh is first traversed. Of course, it is also possible that newCh completes the traversal at this time. In this case, the vnode between newStartIdx and newEndIdx is added. Call addVnodes to insert all of them to the back of before. before is usually null. AddVnodes calls the insertBefore operation dom node. Let's take a look at the insertBefore documentation: parentElement. insertBefore (newElement, referenceElement)

If referenceElement is null, newElement is inserted to the end of the subnode. If newElement is already in the DOM tree, newElement will first be removed from the DOM tree. Therefore, if before is null, newElement is inserted to the end of the subnode.

2. newStartIdx> newEndIdx. It can be considered that newCh is traversed first. At this time, the vnode between oldStartIdx and oldEndIdx does not exist in the new subnode. Call removeVnodes to delete them from the dom.

Summary

The above section describes the Diff algorithm used to parse Vue 2.5. I hope it will help you. If you have any questions, please leave a message and I will reply to you in a timely manner. Thank you very much for your support for the help House website!

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.