1.我们知道vue使用的是虚拟DOM去减少对真是DOM的操作次数,来提升页面的运行的效率。那他的内部原理是怎样的呢。首先vue和react在更新dom时,使用的算法基本相同,都是基于anabbdom。当浏览器的页面数据发生变化时,vue不会立即渲染。二十经过diff算法,判断出哪些是不需要变化的,那些是需要变化更新的,只需要更新那些需要更细的DOM就可以了,这样就减少了很多不必要的DOM操作,在很大程度上提高了性能。vue内部就是使用了这样的抽象节点VNode,它是对真实DOM的抽象,所以他不依赖任何平台,包括浏览器,weex,甚至是node平台也可以 对这样一颗抽象DOM树进行创建删除修改等操作。
在vue早期版本中1.0中,每个数据都对应一个Watcher;而在vue2.x中一个组件对应一个Watcher,这样当我们的数据变化的时候,In the set function,the notify function of Dep will be triggered to notify the watcher to execute vm._update(vm._render(), hydrating)method to update the view,Let’s take a look_Update method
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
//Vue.prototype.__patch__ is injected in entry points
//based on the rendering backednd used.
// 基于后端渲染Vue.prototype.__patch__被用来作为一个入口
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly*/)
} else {
//updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
/*更新新的实例对象的__vue__*/
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
//if parent is a HOC, upadate its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
//update hook is called by the scheduler to ensure that children are
//update in a parent's updated hook.
}
Obviusly, we can see _The update method will patch the incoming vnode and the old vnode.
Let’s take a look at what happens in the patch function.
The patch function compares the new and old nodes, and then determines which nodes need to be modified. Only these nodes needs to be modifie,so that the DOM can be updated more eddifciently. Let’s take a look at the code first.
return function patch (oldVnode, vnode, hydrating, removeOnly){
/*Vnode dose not exist. Call the destroy hook to delete the node*/
if (isUndef(vnode)) {
if (isDef(oldVnode) invokeDestroyHook(oldVnode))
returns
}
let isInitialPath = false
const insetedVnodeQueue = {}
/*oldVnode does not exist. Create a new node directly.*/
if (isUndef(oldVnode)) {
//empty mount (likely as component),create new root element
isInitialPath = true
createElm(vnode, insetedVnodeQueue)
} else {
/*Mark whether the old vnode has nodeType*/
const isRealElement = isDef(oldVnode.nodeType)
if(!isRealElement && sameVnode(oldVnode.nodeType)) {
//patch existing root node
/*When it is the same node,modify the existing node directly*/
patchVnode(oldVnode, vnode, insetedVnodeQueue, null, null, removeOnly)
} else {
if (isRealElement) {
//mounting to a real element
//check if this is server-rendered content and if we can perform
//a successful hydration
if (oldVnode.nodeType === 1 && oldCnode.hasAttribute(SSR_ATTR)) {
/*When the old vnode is the element rendered by the sever,
the drawing is marked as true*/
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)){
//need to merge to real DOM
if (hydrating(oldVnode, vnode, insertedVnodeQueue)) {
//call the insert hook
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
} else if(process.env.NODE_ENV !== 'production') {
warn(
)
}
}
//either not server-rendered,or hydration failed.
//create an empty node and replace it
oldVnode = emptyNodeAt(oldVnode)
}
//replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
//create new node
createElm(
vnode,
insertedVnodeQueue,
//extremely rare edge case: do not insert if old element is in a leaving
//transition.Only happen when combining transition +
//keep-alive + HOCs. (#4590)
oldElm.leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
//update parent placeholder node element, recursively
if(isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for( let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i] (ancestor)
}
ancestor.elm = vnode.elm
if (patchable) {
// Call create callback
for(var i =0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
//#6513
//invoke insert hooks that may have been
// merged by create hooks.e.g. for directives that
// uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
//start at index 1 to avoid re-invoking component mounted hook
for (let i=1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
//destroy old node
if (isDef(parentElm)) {
removeVnodes([oldVnode.tag],0 ,0)
} else if (isDef(oldVnode.tag)){
// call destroy hook
invokeDestroyHook(oldVnode)
}
}
}
}
//call insert hook
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}


Vue’s diff algorithm compares nodes of the same layer, so its time complexity is only O(n),and its
algorithm is very efficient.From the code, we can also see that samevnode will be used in patch to judge whether the old ndoe and the new node are the same node.If so,further patchvnode will be carried out. Otherwise,a new DOM will be created and the old DOM will be removed.
Let’s take a look at how samevnode determines that two nodes are the same node.
/*
To judge whether two vnode nodes are the same node,the following
conditions need to be met
"Key": "same"
Tag (tag name of the current node)
isComment (whether it is a comment node) is the same
Whether data(the object corresponding to the current node,which contains some
special data information, is a vnodedata type,you can refer to the data information
in vnodedata type) is defined
When the tag is ,the type must be the same
*/
function sameVnode (a,b){
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === isDef(b.data) &&
sameInputType(a,b)
) || (
isTrue(a.isAsyncPlaceholder) &&
isUndef(b.asyncFactory.error)
)
)
)
}
/*
Some browsers do not support dynamically changing type for
so they need to be treated as different nodes
*/
function sameInputType (a,b) {
if (a.tag !== 'input') return true
let i
const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
}
Samevnode judges whether the two node nodes are the same by comparing whether the key,tag,comment node and data indormation of the two nodes are equal,and makes a separate judgment on the input tag in order to be compatible with different browsers.
//Diff algorithm comparison node
function patchVnode (
oldVnode,
vnode,
insertedVnodeQueue,
ownerArray,
index,
removeOnly
) {
// If the two vnode nodes are the same, return directly
if (oldVnode === vnode) {
return
}
if (isDef(vnode.elm) && isDef(ownerArray)) {
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode)
}
const elm = vnode.elm = oldVnode.elm
if (isTrue(oldVnode.isAsyncPlaceHolder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldCnode.elm, vnode, insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}
/*
reuse element for static tress.
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 (isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce) )
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
//Execute come component hooks
// If there is data.hook.prepatch,execute it first
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
/* i = data.hook.prepatch,If it exists,见"./create-component componentVNodeHooks".
*/
i(oldVnode, vnode)
}
// Find whether there are children in the old and new nodes
const oldCh = oldVnode.children
const ch = vnode.children
// Property update
if (isDef(data) && isPatchable(vnode)) {
// Teke out the array of attribute updates in cbs [attrFn, classFn, ...]
//Call update callback and update hook
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)
}
// Determine whether it is an element
if (isUndef(vnode.text)) {
//If this vnode node has no text
//Both have children
if ( isDef(old) && isDef(ch)) {
//If both new and old nodes have children, diff the children and
// call updatechildren
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
/*
If the old node has no child nodes and the new node has child nodes,clear the
text content of elm first,and then add child nodes for the current node
*/
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(ch)
}
if (isDef(oldVnode.text)) nodeOps.setTextContext(elm, '')
addVnode( elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
//When the new node has no children and the old node has children,
//remove all the children of ele
removeVnodes(oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
/*
When the new an old nodes have no child nodes,it is only the replacement of text.
Because the new node text does not exist in this logic, the text of ele is
directly removed
*/
nodeOps.setTextContent(elm, '')
}
}else if (oldVnode.text !== vnode.text) {
// When the text of new and old nodes is different,
// replace this text directly
nodeOps.setTextContent(elm, vnode.text)
}
// Call the postpatch hook
if (isDef (data)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i (oldVnode, vnode)
}
}
The process of patchvnode is as follows:
1.If oldvnodes and vnode are the same object,they will be returned directly after a long time,and there is no need to update them
2.If the old and new vnodes are static, and their keys are the same (representing the same node), and the new vnode is clone or marked with once (marked with the v-once attribute,
onlu render once), then it is only necessary to replace elm and componentstance.
3.If vnode Text is not a text node.Both new and old nodes have children.When the children of the new and old nodes are different,the child node is diffed and updatechildren is called.This updatechildren is also the core of diff.
4.If the old node has no child nodes and the new node has child nodes,clear the text content of the dom of the old node first, and then add child nodes for the crrent DOM ndoe
5.When the new node has no children and the old node has children,remove all the children of the DOM node.
6.When the new and old nodes have no child nodes, it is only a text replacement.
The dom of our page is a tree structure.The patchvnode method mentioned above reuses the same DOM element. If both the old and new vnode objects have child elements,how should we compare the reused elements? This is what our updatechildren method does
// Diff core method,compatative optimization
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[newEndIdx]
let oldEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
/*
removedOnly is a special flag used only by
to ensure removed elements stay in correct relative positions
during leaving transitions
*/
const canMove = !removeOnly
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(newCh)
}
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
//To the right
oldStartVnode = oldCh[++oldStartIdx] //Vnode has been moved left
} else if (isUndef(oldEndIdx)) {
//To the left
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
/*
In fact,the first four cases are the same vnode when the key isspecified.
You can patch the vnode directly.Compare the two end nodes of oldch and
newch 2*2=4 casesrespectively
*/
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) {
//Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
canMove && nodeOps.insertBefore(parentElm, oldStart, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--oldEndIdx]
} else if (sameVnode (oldEndVnode, newStartVnode)) {
//Vnode moved left
patchVnode( oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
/*
Generate a hash table with a key corresponding to the key of the old vnode
(it will be generated only when the key is undefined for the first time,
which also paves the way for detecting duplicate key values later)
For example,children is like this: [{xx: xx, key: 'key0'},{xx: xx, key: 'key1'},
{xx: xx, key: 'key2'}] beginIdx = 0 endIdx = 2
result generation{key0: 0, key1: 1, key2: 2}
*/
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdex(oldCh, oldStartIdx,
oldEndIdx)
/*
If there is a key in the new vnode node of newstartvnode,the value of this
node will be returned idxInOld(That id,the number of nodes,subscript)
*/
idxInOld = isDef(newStartVnode.key)? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) {
//New element
/*
newStartVnode If there is no key or the key is not found in the old
node,a new node is created
*/
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm,
false, newCh, newStartIdx)
} else {
/*Get the old node with the same key*/
vnodeToMove = oldCh(idxInOld)
if (sameVnode(vnodeToMove, newStartVnode)) {
/*If the new vnode is the same vnode as the obtained node with the same
key,patchvnode is performed
*/
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm,
false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
if (oldStartIdx > oldEndIdx) {
/*
After all the comparisons are completed,it is found that oldstartIdx > oldEndIdx,
It means that the old nodes have been traversed,and there are more new nodes than
the old nodes, so the new nodes that come out at this time need to be created one
by one and added to the real DOM
*/
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
/*
If newStartIdx > newendIdx is found after all comparisons are completed,
it means that the new node has been traversed,The old node is redundant.
At this time, the redundant old node needs to be removed from the real DOM
*/
removeVnode(oldCh, oldStartIdx, oldEndIdx)
}
}
At first glance, this piece of code may be a bit confusing.The specific content is not complicated.Let's
take a general look at the whole judgment process first, and then discuss it in detail through several examples.
Oldstartidx,newstartidx,oldendidx, and newEndIdx are pointers.I believe everyone knows what each of
them means.We will move the pointer continuously throughout the compadison process.
oldStartVnode,newStartVnode,oldEndVnode,newEndVnode correspond to the above pointers one-to-one,
and are the vnode nodes they point to.
The while loop stops after the oldch or newch traversal ends,otherwise the loop process will be continuously executed.The whole process is divided into the following situations:
1.If oldStartVnode is not defined, the starting pointer of oldch array traversal will be moved one bit later.
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] //Vnode has been moved left
}
Note: see the seventh case.If the key value is the same,it may be set to undefined
2.If oldendvnode is not defined, the starting pointer of oldvh array traversal is moved forward by one bit.
else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
}
Note: see the seventh case.If the key value is the same,it may be set to undefined
3.sameVnode(oldStartVnode, newStartVnode),here it is judged whether the object pointed to by the two array start pointers can be reused.If it returns true, first call the patchvnode method to reuse the DOM element and recursively compare the child elements, and then move the starting pointers of oldch and newch
by one bit respectively.
else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
}
4.Samevnode( oldEndVnode, newEndVnode),here it is judged whether the objects pointed to by the two array end pointers can be reused.If it returns true, first call the patchcnode method to reuse DOM elements and then moce the end pointers of oldch and newch forward by one bit respectively.
else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
}
5.sameVnode(oldStartVnode, newEndVnode),here,it is judged whether the object pointed to by the oldch start pointer and the object pointed to by the newCh end pointer can ve reused.If it return true, call the patchvnode method to reuse the DOM element and recursively compare the child elements.Because the reused element is the element indicated by the end pointer in the newch,insert it in front of oldendvnode.elm.
Finally,the start pointer of oldch is moved back by one bit, and the start pointr of newch is moved forward by
one bit.
else if (sameVnode(oldStartVnode, newEndVnode) ) {
//Vnode moced right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
canMove && ndoeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
}
6.sameVnode(oldEndVnode, newStartVnode),here it is judged whether the object pointed to by the oldch end pointer and the object pointed to by the newch start pointer can be reused.if return true,which first call patchVnode methods reused dom point and cmpare children point,because the reused element is the element indicated by the starting pointer in newCh,so inserte it front of oldStartVnode.elm.Finally,the end pointer of oldch is moved forward by one bit,the start pointer of newCh is moved back by one bit respectively.
else if (sameVnode(oldEndVnode, newStartVnode)) {
//Vnode moved left
patchVndoe(oldEndVnode, newStartVnode, insertedVnodeQueue)
canMove && ndoeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
}
7.If the above six conditions are not stisfied,Then go here.The previeous comparison is a combination of head and tail.The situtation here is slightly more complicated.In fact,it maninly reuses elements according
to the key value.
(1) Traverse the oldch array,find the object with the key in it,and generate a new object oldkeytoidx with the key as the key and the index value as the value.
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i,key
const map = {}
for (i=beginIdx; i <= endIdx ; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
(2) Query whether newstartvnode has a key value and find out whether oldKeyToIdx has the same key
idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
(3)If newStartVnode has no key or oldKeyToIdx has not the same key,the createElm method is called to create a new element,and the starting index of newCh is moved one bit later.
if (isUndef(idxInOld)) {
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
}
(4)elmToMove to save will be moved,if sameVnode(elmToMove, newStartVnode) return true,Description :reuseable,then first call the patchVnode method reuse dom element and recursively compare children elements,reset oldCh’s element is undefined, then insert current element in front of oldStartVnode.elm,The
starting index of newCh is moved back by one bit.If sameVnode(elmToMove, newStartVnode) returns false,
For example, if the tag names are different,the createElm method is called to create a new element, and the
staring index of newCh is moved one bit later.
elmToMove = oldCh[inxInOld]
if (sameVnode(elmToMove, newStartVnode, insertedVnodeQueue)) {
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
} else {
//same key but different element,treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
}
参考文章
https://www.cnblogs.com/bejamin/p/14495913.html