深入理解Vue2.x的虚拟DOM diff原理
前言
经常看到讲解Vue2的虚拟Domdiff原理的,但很多都是在原代码的基础上添加些注释等等,这里从0行代码开始实现一个Vue2的虚拟DOM
实现VNode
src/core/vdom/Vnode.js
exportclassVNode{ constructor( tag,//标签名 children,//孩子[VNode,VNode], text,//文本节点 elm//对应的真实dom对象 ){ this.tag=tag; this.children=children this.text=text; this.elm=elm; } } exportfunctioncreateTextNode(val){ //为什么这里默认把elm置为undefined,不直接根据tag用document.createElement(tagName)把elm赋值?而要等后面createElm时候再赋值呢? returnnewVNode(undefined,undefined,String(val),undefined) } exportfunctioncreateCommentNode(tag,children){ if(children){ for(vari=0;i定义一个Vnode类,创建节点分为两类,一类为text节点,一类非text节点
src/main.js
import{VNode,createCommentNode}from'./core/vdom/vnode' varnewVonde=createCommentNode('ul',[createCommentNode('li',['item1']),createCommentNode('li',['item2']),createCommentNode('li',['item3'])])在main.js就可以根据Vnode生成对应的Vnode对象,上述代码对应的dom表示
- item1
- item2
- item3
先实现不用diff把Vnode渲染到页面中来
为什么先来实现不用diff渲染Vnode的部分,这里也是为了统计渲染的时间,来表明一个道理。并不是diff就比非diff要开,虚拟DOM并不是任何时候性能都比非虚拟DOM要快
先来实现一个工具函数,不熟悉的人可以手工敲下代码熟悉下
//真实的dom操作 //src/core/vdom/node-ops.js exportfunctioncreateElement(tagName){ returndocument.createElement(tagName) } exportfunctioncreateTextNode(text){ returndocument.createTextNode(text) } exportfunctioncreateComment(text){ returndocument.createComment(text) } exportfunctioninsertBefore(parentNode,newNode,referenceNode){ parentNode.insertBefore(newNode,referenceNode) } exportfunctionremoveChild(node,child){ node.removeChild(child) } exportfunctionappendChild(node,child){ node.appendChild(child) } exportfunctionparentNode(node){ returnnode.parentNode } exportfunctionnextSibling(node){ returnnode.nextSibling } exportfunctiontagName(node){ returnnode.tagName } exportfunctionsetTextContent(node,text){ node.textContent=text } exportfunctionsetAttribute(node,key,val){ node.setAttribute(key,val) }
src/main.js
import{VNode,createCommentNode}from'./core/vdom/vnode' importpatchfrom'./core/vdom/patch' varcontainer=document.getElementById("app"); varoldVnode=newVNode(container.tagName,[],undefined,container); varnewVonde=createCommentNode('ul',[createCommentNode('li',['item1']),createCommentNode('li',['item2']),createCommentNode('li',['item3'])]) console.time('start'); patch(oldVnode,newVonde);//渲染页面 console.timeEnd('start');
这里我们要实现一个patch方法,把Vnode渲染到页面中
src/core/vdom/patch.js
import*asnodeOpsfrom'./node-ops' importVNodefrom'./vnode' exportdefaultfunctionpatch(oldVnode,vnode){ letisInitialPatch=false; if(sameVnode(oldVnode,vnode)){ //如果两个Vnode节点的根一致开始diff patchVnode(oldVnode,vnode) }else{ //这里就是不借助diff的实现 constoldElm=oldVnode.elm; constparentElm=nodeOps.parentNode(oldElm); createElm( vnode, parentElm, nodeOps.nextSibling(oldElm) ) if(parentElm!=null){ removeVnodes(parentElm,[oldVnode],0,0) } } returnvnode.elm; } functionpatchVnode(oldVnode,vnode,removeOnly){ if(oldVnode===vnode){ return } constelm=vnode.elm=oldVnode.elm constoldCh=oldVnode.children; constch=vnode.children if(isUndef(vnode.text)){ //非文本节点 if(isDef(oldCh)&&isDef(ch)){ //都有字节点 if(oldCh!==ch){ //更新children updateChildren(elm,oldCh,ch,removeOnly); } }elseif(isDef(ch)){ //新的有子节点,老的没有 if(isDef(oldVnode.text)){ nodeOps.setTextContent(elm,''); } //添加子节点 addVnodes(elm,null,ch,0,ch.length-1) }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); } } functionupdateChildren(parentElm,oldCh,newCh,removeOnly){ //这里认真读下,没什么难度的,不行的话也可以搜索下图文描述这段过程的 letoldStartIdx=0; letnewStartIdx=0; letoldEndIdx=oldCh.length-1; letoldStartVnode=oldCh[0]; letoldEndVnode=oldCh[oldEndIdx]; letnewEndIdx=newCh.length-1; letnewStartVnode=newCh[0] letnewEndVnode=newCh[newEndIdx] letrefElm; constcanMove=!removeOnly while(oldStartIdx<=oldEndIdx&&newStartIdx<=newEndIdx){ if(isUndef(oldStartVnode)){ oldStartVnode=oldCh[++oldStartIdx] }elseif(isUndef(oldEndVnode)){ oldEndVnode=oldCh[--oldEndIdx] }elseif(sameVnode(oldStartVnode,newStartVnode)){ patchVnode(oldStartVnode,newStartVnode) oldStartVnode=oldCh[++oldStartIdx] newStartVnode=newCh[++newStartIdx] }elseif(sameVnode(oldEndVnode,newEndVnode)){ patchVnode(oldEndVnode,newEndVnode) oldEndVnode=oldCh[--oldEndIdx]; newEndVnode=newCh[--newEndIdx]; }elseif(sameVnode(oldStartVnode,newEndVnode)){ patchVnode(oldStartVnode,newEndVnode); //更换顺序 canMove&&nodeOps.insertBefore(parentElm,oldStartVnode.elm,nodeOps.nextSibling(oldEndVnode.elm)) oldStartVnode=oldCh[++oldStartIdx] newEndVnode=newCh[--newEndIdx] }elseif(sameVnode(oldEndVnode,newStartVnode)){ patchVnode(oldEndVnode,newStartVnode) canMove&&nodeOps.insertBefore(parentElm,oldEndVnode.elm,oldStartVnode.elm) oldEndVnode=oldCh[--oldEndIdx] newStartVnode=newCh[++newStartIdx] }else{ createElm(newStartVnode,parentElm,oldStartVnode.elm) newStartVnode=newCh[++newStartIdx]; } } if(oldStartIdx>oldEndIdx){ //老的提前相遇,添加新节点中没有比较的节点 refElm=isUndef(newCh[newEndIdx+1])?null:newCh[newEndIdx+1].elm addVnodes(parentElm,refElm,newCh,newStartIdx,newEndIdx) }else{ //新的提前相遇删除多余的节点 removeVnodes(parentElm,oldCh,oldStartIdx,oldEndIdx) } } functionremoveVnodes(parentElm,vnodes,startIdx,endIdx){ for(;startIdx<=endIdx;++startIdx){ constch=vnodes[startIdx]; if(isDef(ch)){ removeNode(ch.elm) } } } functionaddVnodes(parentElm,refElm,vnodes,startIdx,endIdx){ for(;startIdx<=endIdx;++startIdx){ createElm(vnodes[startIdx],parentElm,refElm) } } functionsameVnode(vnode1,vnode2){ returnvnode1.tag===vnode2.tag } functionremoveNode(el){ constparent=nodeOps.parentNode(el) if(parent){ nodeOps.removeChild(parent,el) } } functionremoveVnodes(parentElm,vnodes,startIdx,endIdx){ for(;startIdx<=endIdx;++startIdx){ constch=vnodes[startIdx] if(isDef(ch)){ removeNode(ch.elm) } } } functionisDef(s){ returns!=null } functionisUndef(s){ returns==null } functioncreateChildren(vnode,children){ if(Array.isArray(children)){ for(leti=0;i这就是完整实现了
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。