vue页面更新patch的

2021-02-25 12:48:31 181
爱秀

本文将为大家详细介绍“vue页面更新patch的”,内容步骤清晰详细,细节处理妥当,而小编每天都会更新不同的知识点,希望这篇“vue页面更新patch的”能够给你意想不到的收获,请大家跟着小编的思路慢慢深入,具体内容如下,一起去收获新知识吧。

Vue的优点

Vue具体轻量级框架、简单易学、双向数据绑定、组件化、数据和结构的分离、虚拟DOM、运行速度快等优势,Vue中页面使用的是局部刷新,不用每次跳转页面都要请求所有数据和dom,可以大大提升访问速度和用户体验。

patch的流程

组件页面渲染时,将render返回的新vnode(新节点)和组件实例保存的vnode(旧节点)作为参数,调用patch方法,更新DOM。

判断两个节点是否相同

处理过程中,需要判断节点是否相同。相同节点需要满足以下条件:

  • key相同

  • 标签类型相同

  • 注释节点标识相同,都是注释节点,或者都不是注释节点

  • data的值状态相同,或者都有值,或者都没值

functionsameVnode(a,b){//判断两个VNode节点是否是同一个节点
return(
a.key===b.key&&//key相同
(
a.tag===b.tag&&//tag相同
a.isComment===b.isComment&&//注释节点标识相同
isDef(a.data)===isDef(b.data)&&//data值状态相同
sameInputType(a,b)//input的type相同
)
)
}

patch方法

patch判断流程如下:

a) 如果新节点为空,此时旧节点存在(组件销毁时),调用旧节点destroy生命周期函数

b) 如果旧节点为空,根据新节点创建DOM

c) 其他(如果新旧节点都存在)

  • a) 旧节点不是DOM(组件节点),且新旧节点相同

    • 执行patchVnode

  • b) 旧节点是DOM元素或者两个节点不相同

    • 创建新节点DOM,销毁旧节点以及DOM。

functionpatch(oldVnode,vnode,hydrating,removeOnly){
if(isUndef(vnode)){
if(isDef(oldVnode)){invokeDestroyHook(oldVnode);}
return
}
...
if(isUndef(oldVnode)){
isInitialPatch=true;//组件初始加载
createElm(vnode,insertedVnodeQueue);
}else{
varisRealElement=isDef(oldVnode.nodeType);
if(!isRealElement&&sameVnode(oldVnode,vnode)){
patchVnode(oldVnode,vnode,insertedVnodeQueue,null,null,removeOnly);
}else{
...
varoldElm=oldVnode.elm;
varparentElm=nodeOps.parentNode(oldElm);//获取父元素
//createnewnode
createElm(
vnode,
insertedVnodeQueue,
oldElm._leaveCb?null:parentElm,
nodeOps.nextSibling(oldElm)//获取紧跟的弟弟元素
);
if(isDef(parentElm)){
removeVnodes(parentElm,[oldVnode],0,0);//销毁旧节点以及DOM元素
}elseif(isDef(oldVnode.tag)){
invokeDestroyHook(oldVnode);
}
}
}
invokeInsertHook(vnode,insertedVnodeQueue,isInitialPatch);
returnvnode.elm
}
}

patchVnode方法

当两个节点相同时,执行patchVnode方法。在处理各种情况之前,会将旧节点elm属性值赋值给新节点的elm属性,保持elm保持一致。

具体流程如下:

a)如果新旧节点完全相同(引用相同 oldVnode === vnode)

  • 直接返回不处理

b) 如果新节点不是文本节点

  • a)都存在子节点,新旧节点的子节点数组引用不同(oldCh !== ch)

    • updateChildren

  • b)新节点有子节点,旧节点没有

    • 1)查重子节点(key)

    • 2)如果旧节点是文本节点,先清空文本

    • 3)创建子节点DOM元素

  • c)旧节点有子节点,新节点没有

    • 移除子节点以及DOM

  • d)旧节点是文本节点

    • 清除文本

  • c)如果新节点是文本节点,并且和旧节点文本不相同

    • 则直接替换文本内容。

  • d)其他(新节点是文本节点,并且和旧节点相同)

    • 不处理

functionpatchVnode(
oldVnode,
vnode,
insertedVnodeQueue,
ownerArray,
index,
removeOnly
){
if(oldVnode===vnode){
return
}
...
if(isUndef(vnode.text)){
if(isDef(oldCh)&&isDef(ch)){
if(oldCh!==ch){updateChildren(elm,oldCh,ch,insertedVnodeQueue,removeOnly);}
}elseif(isDef(ch)){
if(process.env.NODE_ENV!=='production'){
checkDuplicateKeys(ch);
}
if(isDef(oldVnode.text)){nodeOps.setTextContent(elm,'');}
addVnodes(elm,null,ch,0,ch.length-1,insertedVnodeQueue);
}elseif(isDef(oldCh)){
removeVnodes(elm,oldCh,0,oldCh.length-1);
}elseif(isDef(oldVnode.text)){
nodeOps.setTextContent(elm,'');
}
}elseif(oldVnode.text!==vnode.text){
nodeOps.setTextContent(elm,vnode.text);
}
...
}

updateChildren方法

updateChildren方法处理相同新旧节点的子节点。方法定义了以下变量(updateChildren的节点都表示的是子节点):

varoldStartIdx=0;//表示当前正在处理的旧起始节点序号
varnewStartIdx=0;//表示当前正在处理的新起始节点序号
varoldEndIdx=oldCh.length-1;//表示当前正在处理的旧结尾节点序号
varoldStartVnode=oldCh[0];//表示当前正在处理的旧起始节点
varoldEndVnode=oldCh[oldEndIdx];//表示当前正在处理的旧结尾节点
varnewEndIdx=newCh.length-1;//表示当前正在处理的新结尾节点序号
varnewStartVnode=newCh[0];//表示当前正在处理的新起始节点
varnewEndVnode=newCh[newEndIdx];//表示当前正在处理的新结尾节点
varoldKeyToIdx,//尚未处理的旧节点key值映射
idxInOld,//与新节点key值相同的旧节点序号
vnodeToMove,//与新节点key值相同的旧节点
refElm;//指向当前正在处理的新结尾节点的后一个节点(已处理)的DOM元素

根据新旧节点的对比结果,更新DOM元素,此过程并不改变新旧节点的排序。序号指向正在处理的节点,分别是新旧节点的起始和结尾节点。对比过程以新起始节点为主导,对比方向是由两侧向中间。优先比对新旧节点的起始节点和结尾节点,再查找与新起始节点相同的且未处理的旧节点。当旧节点全部处理完(旧起始和结尾序号重叠),此时新节点可能未处理完,就添加新节点DOM元素。当新节点全部处理完(新起始和结尾序号重叠),可能存在旧节点,就删除旧节点DOM元素。

具体流程如下:

新旧子节点的起始序号不大于结尾序号时,执行以下流程:

a)如果旧子节点两侧存在undefined节点

  • 旧起始节点undefined,oldStartVnode = oldCh[++oldStartIdx]

  • 旧结尾节点undefined,oldEndVnode = oldCh[--oldEndIdx]

b)新旧子节点的起始节点相同(前后比较)

  • patchVNode更新DOM内容

  • oldStartVnode = oldCh[++oldStartIdx]

  • newStartVnode = newCh[++newStartIdx]

c)新旧子节点的结尾节点相同(前后比较)

  • patchVNode更新DOM内容

  • oldEndVnode = oldCh[--oldEndIdx]

  • newEndVnode = newCh[--newEndIdx]

d)旧起始节点和新结尾节点相同(前后比较)

  • patchVNode更新DOM内容

  • 将旧起始节点DOM添加到旧结尾节点DOM前面

  • oldStartVnode = oldCh[++oldStartIdx]

  • newEndVnode = newCh[--newEndIdx]

e)旧结尾节点和新起始节点相同(前后比较)

  • patchVNode更新DOM内容

  • 将旧结尾节点DOM添加到旧起始节点DOM前面

  • oldEndVnode = oldCh[--oldEndIdx]

  • newStartVnode = newCh[++newStartIdx]

f)其他(缓存尚未处理的旧节点key值,依此判断旧节点中是否存在和新起始节点相同的节点)

  • a)尚未处理的旧节点中不存在与新起始节点相同的节点

    • 创建新节点DOM并添加到旧起始节点DOM的前面

    • newStartVnode = newCh[++newStartIdx]

  • b)旧节点中存在与新起始节点key相同的节点

    • 创建新节点DOM并添加到旧起始节点DOM的前面

    • newStartVnode = newCh[++newStartIdx]

    • patchVode

    • 将相同的旧节点DOM添加到旧起始节点DOM前面

    • 将相同的旧节点置为undefinedoldCh[idxInOld] = undefined

    • newStartVnode = newCh[++newStartIdx]

    • a)旧节点中存在与新起始节点相同的节点

    • b)key相同,但标签类型不同的节点

循环结束

a)如果旧节点遍历完(oldStartIdx > oldEndIdx

  • 把剩余未处理新节点DOM添加到上一个新结尾节点DOM前面(从新起始节点到新结尾节点,都未处理过)

b)如果新节点遍历完(newStartIdx > newEndIdx

  • 移除旧起始和结尾节点以及他们之间的节点的DOM(从旧起始节点到旧结尾节点,可能存在处理过的节点,但处理过已被置为undefined)

functionupdateChildren(parentElm,oldCh,newCh,insertedVnodeQueue,removeOnly){
varoldStartIdx=0;//表示当前正在处理的旧起始节点序号
varnewStartIdx=0;//表示当前正在处理的新起始节点序号
varoldEndIdx=oldCh.length-1;//表示当前正在处理的旧结尾节点序号
varoldStartVnode=oldCh[0];//表示当前正在处理的旧起始节点
varoldEndVnode=oldCh[oldEndIdx];//表示当前正在处理的旧结尾节点
varnewEndIdx=newCh.length-1;//表示当前正在处理的新结尾节点序号
varnewStartVnode=newCh[0];//表示当前正在处理的新起始节点
varnewEndVnode=newCh[newEndIdx];//表示当前正在处理的新结尾节点
varoldKeyToIdx,idxInOld,vnodeToMove,refElm;
...
while(oldStartIdx<=oldEndIdx&&newStartIdx<=newEndIdx){
if(isUndef(oldStartVnode)){
oldStartVnode=oldCh[++oldStartIdx];//Vnodehasbeenmovedleft
}elseif(isUndef(oldEndVnode)){
oldEndVnode=oldCh[--oldEndIdx];
}elseif(sameVnode(oldStartVnode,newStartVnode)){
patchVnode(oldStartVnode,newStartVnode,insertedVnodeQueue,newCh,newStartIdx);
oldStartVnode=oldCh[++oldStartIdx];
newStartVnode=newCh[++newStartIdx];
}elseif(sameVnode(oldEndVnode,newEndVnode)){
patchVnode(oldEndVnode,newEndVnode,insertedVnodeQueue,newCh,newEndIdx);
oldEndVnode=oldCh[--oldEndIdx];
newEndVnode=newCh[--newEndIdx];
}elseif(sameVnode(oldStartVnode,newEndVnode)){//Vnodemovedright
patchVnode(oldStartVnode,newEndVnode,insertedVnodeQueue,newCh,newEndIdx);
canMove&&nodeOps.insertBefore(parentElm,oldStartVnode.elm,nodeOps.nextSibling(oldEndVnode.elm));
oldStartVnode=oldCh[++oldStartIdx];
newEndVnode=newCh[--newEndIdx];
}elseif(sameVnode(oldEndVnode,newStartVnode)){//Vnodemovedleft
patchVnode(oldEndVnode,newStartVnode,insertedVnodeQueue,newCh,newStartIdx);
canMove&&nodeOps.insertBefore(parentElm,oldEndVnode.elm,oldStartVnode.elm);
oldEndVnode=oldCh[--oldEndIdx];
newStartVnode=newCh[++newStartIdx];
}else{
if(isUndef(oldKeyToIdx)){oldKeyToIdx=createKeyToOldIdx(oldCh,oldStartIdx,oldEndIdx);}//缓存尚未处理的旧节点key值
idxInOld=isDef(newStartVnode.key)
?oldKeyToIdx[newStartVnode.key]
:findIdxInOld(newStartVnode,oldCh,oldStartIdx,oldEndIdx);
if(isUndef(idxInOld)){//Newelement
createElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm,false,newCh,newStartIdx);
}else{
vnodeToMove=oldCh[idxInOld];
if(sameVnode(vnodeToMove,newStartVnode)){
patchVnode(vnodeToMove,newStartVnode,insertedVnodeQueue,newCh,newStartIdx);
oldCh[idxInOld]=undefined;
canMove&&nodeOps.insertBefore(parentElm,vnodeToMove.elm,oldStartVnode.elm);
}else{
//samekeybutdifferentelement.treatasnewelement
createElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm,false,newCh,newStartIdx);
}
}
newStartVnode=newCh[++newStartIdx];
}
}
if(oldStartIdx>oldEndIdx){
refElm=isUndef(newCh[newEndIdx+1])?null:newCh[newEndIdx+1].elm;
addVnodes(parentElm,refElm,newCh,newStartIdx,newEndIdx,insertedVnodeQueue);
}elseif(newStartIdx>newEndIdx){
removeVnodes(parentElm,oldCh,oldStartIdx,oldEndIdx);
}
}

updateChildren的示例:

1.左边表示新旧节点,节点下面标识起始和结尾节点(即正在处理的节点)。右边表示当前的DOM。

2.新节点的起始和结尾节点与旧节点的起始和结尾节点互不相同,并且在旧节点中未找到与新起始节点(新节点f)相同的节点。所以创建节点f的DOM并添加到旧起始节点(旧节点a)DOM的前面,然后新起始节点序号加1,表示新节点f已处理,当前正在处理新起始节点c。

3.新节点的起始和结尾节点与旧节点的起始和结尾节点互不相同,但在旧节点中找到与新起始节点(节点c)相同的节点。所以将旧节点c的DOM添加到旧起始节点(旧节点a)DOM的前面,旧节点c置空,然后新起始节点序号加1,表示新节点c已处理,当前正在处理新起始节点e。

4.新起始节点(新节点e)和旧结尾节点(旧节点e)相同。更新旧节点e的DOM内容,并将旧节点e的DOM移动到旧起始节点(旧节点a)DOM的前面,旧结尾节点序号减1,新起始节点加1,表示新旧节点e已处理,当前正在处理的是新起始节点g和旧结尾节点d。

5.新结尾节点(新节点d)和旧结尾节点(旧节点d)相同。仅更新旧节点d的DOM内容。新结尾节点序号减1,旧结尾节点序号减1,表示新旧节点d已处理,当前正在处理的是新结尾节点g和旧结尾节点c。由于旧节点c为空,则旧结尾节点为b。

6.新节点的起始和结尾节点与旧节点的起始和结尾节点互不相同,并且在旧节点中未找到与新起始节点(新节点g)相同的节点。所以创建节点g的DOM并添加到旧起始节点(旧节点a)DOM的前面,然后新起始节点序号加1,表示新节点g已处理,当前正在处理新起始节点d。

7.由于新起始和结尾节点序号重叠,新节点已经处理完毕,存在尚未处理的旧节点,则移除未处理的旧节点DOM。

8.结束,最终的DOM。

如果你能读到这里,小编希望你对“vue页面更新patch的”这一关键问题有了从实践层面最深刻的体会,具体使用情况还需要大家自己动手实践使用过才能领会,如果想阅读更多相关内容的文章,欢迎关注恰卡编程网行业资讯频道!

分享
海报
181
上一篇:使用Vue的小技巧下一篇:Vue的响应式更新比React快的原因

忘记密码?

图形验证码