当前位置: 首页 > Web前端 > vue.js

VirtualDom-Diff的patchVnode方法

时间:2023-04-01 00:46:45 vue.js

该方法用于真正比较新旧节点,得到最小应该变化的DOM,然后直接更新DOM。以下是需要打补丁的几种情况。这些情况都会有对应的真实DOM测试用例来验证。functionpatchVnode(oldVnode,vnode){constelm=vnode.elm=oldVnode.elm;const{children:oldCh}=oldVnode;const{children:ch}=vnode;if(!vnode.text){if(oldCh&&ch){//旧节点和新节点都有孩子[孩子是vnode对象中的孩子]}elseif(oldCh){//旧节点有孩子,但是新节点没有孩子}elseif(ch){//新节点有孩子,但旧节点没有孩子}elseif(oldVnode.text){//旧节点是文本节点,但文本新节点的为空}}elseif(oldVnode.text!==vnode.text){//新旧节点都是文本节点,文本不同}}1.constelm=vnode.elm=oldVnode。榆树;vnode代表一个新节点,此时没有elm属性。createElm方法之后,vnode.children中的子节点都带有elm属性。此时只有vnode没有elm属性,能进入patchVnode方法的新旧节点肯定都通过了sameVnode方法的判断,说明它们的节点几乎一样,所以新节点可以使用旧节点的elmif(sameVnode(oldVnode,vnode)){patchVnode(oldVnode,vnode)}2。!vnode.text可以进入这个条件,有两种可能:vnode是一个文本节点,但是文本节点的文本是一个false值constvnode={text:0/false/''}vnodehaschildrenconstvnode={tag:'div',children:[{...}]}注意:Vnode对象有很多属性,没有列出的属性默认值为undefined,所以!vnode.text===!undefined会进入这个逻辑。换句话说,文本节点和带有子节点的节点是互斥的。3.oldCh&&ch新旧节点都有子节点,可以进入patchVnode方法,说明新旧节点几乎一样。需要做的是比较他们的子节点更新DOMif(sameVnode(oldVnode,vnode)){patchVnode(oldVnode,vnode)}if(oldCh&&ch){if(oldCh!==ch)updateChildren(elm,旧Ch,ch);//updateChildren方法有点复杂,是Diff的核心方法}最终页面效果Diff前后对应的DOM结构对应DOM的Vnode对象constapp=document.getElementById('app');constspan=document.querySelector('span');constspan_text=span.childNodes[0];常量注释=[...app.childNodes].filter(el=>el.nodeType===8)[0]constul=document.getElementsByTagName('ul')[0];constlis=ul.children;constoldVnode={标签:'div',data:{attrs:{id:'app'}},elm:app,//旧节点的Vnode对象上会有一个elm属性,表示该Vnode对应的真实DOM元素children:[{tag:'span',elm:span,children:[{text:'一到两三英里',elm:span_text}]},{text:'我是评论',isComment:true,elm:评论},{tag:'ul',elm:ul,children:[{tag:'li',elm:lis[0],children:[{text:'item1',elm:lis[0].childNodes[0]}]},{tag:'li',elm:lis[1],children:[{text:'item2',elm:lis[1].childNodes[0]}]},{tag:'li',elm:lis[2],children:[{text:'item3',elm:lis[2].childNodes[0]}]},]}]}//新节点是没有elm属性的constvnode={tag:'div',data:{attrs:{id:'app'}},children:[{tag:'span',children:[{text:'烟村四五房'}]},]}从图例和新旧vnode可以看出,它们都有chidlren子节点,所以本例会进入patchVnode方法的oldCh&&ch逻辑。我举个updateChildren方法逻辑的例子,把方法放在第一位。一个逻辑框架代码:functionupdateChildren(parentElm,oldCh,newCh){让oldStartIdx=0;让oldEndIdx=oldCh.length-1;让oldStartVnode=oldCh[0];让oldEndVnode=oldCh[oldEndIdx];让newStartIdx=0;让newEndIdx=newCh。长度-1;让newStartVnode=newCh[0];让newEndVnode=newCh[newEndIdx];让oldKeyToIdx、idxInOld、vnodeToMove、refElm;while(oldStartIdx<=oldEndIdx&&newStartIdx<=newEndIdx){if(sameVnode(oldStartVnode,newStartVnode)){//head和head相同,位置不变,只是使用patch子节点更新子节点DOM}elseif(sameVnode(oldEndVnode,newEndVnode)){//尾尾相同且位置不变,只使用patch子节点,只更新子节点DOM}elseif(sameVnode(oldStartVnode,newEndVnode)){//oldhead==newtailDOM位置需要移动,使用insertBeforeAPI从头移动到末尾}elseif(sameVnode(oldEndVnode,newStartVnode)){//oldend==newheadDOM位置需要移动,从最后一个移到第一个}else{//以上四种类型都不满足,单次查找}}if(oldStartIdx>oldEndIdx){}elseif(newStartIdx>newEndIdx){}}意思是所有的Diff文章都是一样的头尾,一样的尾,旧的head===newhead....等等。一开始看到这个描述的时候我是一头雾水。..我将用一个例子来解释每个案例3.1。newhead===oldhead的意思是:新节点的headvnode约等于旧节点的headvnode,需要做的就是比较它们的child节点有什么区别,从而更新需要更新的子节点DOM如图:从图例可以看出,对于head和head相等的情况,DOM中相同节点(span)的位置不不需要移动,旧节点中剩余的节点的子节点(comment,ul)可以删除。4.oldCh新节点没有,老节点有,需要删除老节点中的这些DOM元素最终页面效果对应DOM对应的Vnode对象前后的DOM结构DiffconstoldVnode={tag:'div',data:{attrs:{id:'app'}},elm:app,children:[{tag:'span',elm:span,children:[{text:'一到二三英里',elm:span_text}]},{text:'我是评论',isComment:true,elm:comment},{tag:'ul',elm:ul,children:[{tag:'li',elm:lis[0],children:[{text:'item1',elm:lis[0].childNodes[0]}]},{tag:'li',elm:lis[1],children:[{text:'item2',elm:lis[1].childNodes[0]}]},{tag:'li',elm:lis[2],children:[{text:'item3',elm:lis[2].气ldNodes[0]}]},]}]}constvnode={tag:'div',data:{attrs:{id:'app'}},}patchVnode逻辑函数patchVnode(oldVnode,vnode){constelm=vnode.elm=oldVnode.elm;const{children:oldCh}=oldVnode;const{children:ch}=vnode;if(!vnode.text){if(oldCh&&ch){}elseif(oldCh){//旧节点有子节点,新节点没有子节点for(constchildofoldCh){if(child){oldVnode.elm.removeChild(child.elm);}}}elseif(ch){}elseif(oldVnode.text){}}elseif(oldVnode.text!==vnode.text){}}5.如果新节点有,旧节点没有,需要插入到最终页面效果DOM对应的DOM前后结构Vnode对象DiffconstoldVnode={tag:'div',data:{attrs:{id:'app'}},elm:app}constvnode={tag:'div',data:{attrs:{id:'app'}},children:[{tag:'span',data:{attrs:{class:'first'}},children:[{text:'一二三英里'}]},{text:'我是评论',isComment:true,},{tag:'ul',data:{attrs:{class:'list'}},children:[{tag:'li',children:[{text:'item1'}]},{tag:'li',children:[{text:'item2'}]},{tag:'li',children:[{text:'item3'}]},]}]}patchVnode逻辑函数patchVnode(oldVnode,vnode){constelm=vnode.elm=oldVnode.elm;const{children:oldCh}=oldVnode;const{children:ch}=vnode;if(!vnode.text){if(oldCh&&ch){}elseif(oldCh){}elseif(ch){//新节点有子节点,旧节点没有for(constchildofch){createElm(child,elm,null);//创建并插入父元素}}elseif(oldVnode.text){}}elseif(oldVnode.text!==vnode.text){}}functioncreateElm(vnode,parentNode,refNode){const{text,标签、孩子、数据、isComment}=vnode;如果(标签){vnode.elm=document.createElement(标签);//生成子节点createChildren(vnode,children);//添加属性if(data){const{attrs}=data;if(attrs){for(constkinattrs){vnode.elm.setAttribute(k,attrs[k]);}}}//将子节点插入父节点insert(parentNode,vnode.elm,refNode);}elseif(isComment){vnode.榆树=document.createComment(文本);//添加注释节点并将其添加到其父元素中insert(parentNode,vnode.elm,refNode);}else{vnode.elm=document.createTextNode(text)//添加一个新的文本节点并将其添加到其父元素中insert(parentNode,vnode.elm,refNode);}}functioncreateChildren(vnode,children){if(Array.isArray(children)){for(constchildofchildren){createElm(child,vnode.elm);}}}functioninsert(parent,newNode,refNode){if(parent){if(refNode){if(refNode.parentNode===parent){//看下图parent.insertBefore(newNode,refNode);}}else{parent.appendChild(newNode);}}}