diffdiff比较的是vnodes,由于dom很少跨层级移动,所以只在同一层级执行vue和react的diff算法大致相同。VNodevue的vnode如下{el:div//引用真实节点,本例为document.querySelector('#id.classA')tagName:'DIV',//节点标签sel:'div#v.classA'//节点选择器data:null,//存储一个节点属性对象,对应节点的el[prop]属性,如onclick,stylechildren:[],//存储的子节点数组,每一个子节点也是一个vnode结构text:null,//如果是文本节点,则对应文本节点textContent,否则为null}React的vnode如下{type:'div',props:{className:'myDiv',},chidren:[{type:'p',props:{value:'1'}},{type:'div',props:{value:'2'}},{type:'span',props:{value:'3'}}]}Vuevue的diff是数据变化时比较和更新的过程,触发更新尝试会生成新的vdom树,用于与同级vnode进行比较。只有当vue认为两个vnode值得比较时,它才会继续比较它的子节点。如果认为不值得比较,则直接删除旧节点,插入新节点函数oldVnode.elletparentEle=api.parentNode(oEl)createEle(vnode)if(parentEle!==null){api.insertBefore(parentEle,vnode.el,api.nextSibling(oEl))api.removeChild(parentEle,oldVnode.el)oldVnode=null}}returnvnode}functionsameVnode(oldVnode,vnode){//两个节点key值相同,sel属性值相同,即认为这两个节点属于同一类型,可以进行下一步比较returnvnode.key===oldVnode.key&&vnode.sel===oldVnode.sel}对同类型节点,进行patch操作,比较其子节点patchVnode(oldVnode,vnode){constel=vnode.el=oldVnode.el//让vnode.el引用当前真实dom,修改el时,vnode.el会同步变化。leti,oldCh=oldVnode.children,ch=vnode.childrenif(oldVnode===vnode)return//新旧节点引用相同,不考虑改变//文本节点比较if(oldVnode.text!==null&&vnode.text!==null&&oldVnode.text!==vnode.text){api.setTextContent(el,vnode.text)}else{updateEle(el,vnode,oldVnode)//对于子节点(两个不同的节点),调用updateChildrenif(oldCh&&ch&&oldCh!==ch){updateChildren(el,oldCh,ch)}elseif(ch){//只有新节点才有子节点,添加新的子节点createEle(vnode)//创建el的子dom}elseif(oldCh){//只有老节点有子节点,删除子节点operationapi.removeChildren(el)}}}针对双方都有子节点的情况,通过updateChildren处理updateChildrenupdateChildren(parentElm,oldCh,newCh){letoldStartIdx=0,newStartIdx=0letoldEndIdx=oldCh.length-1让oldStartVnode=oldCh[0]让oldEndVnode=oldCh[oldEndIdx]让newEndIdx=newCh.length-1让newStartVnode=newCh[0]让newEndVnode=newCh[newEndIdx]让oldKeyToIdx让idxInOld让elmToMove让before<=oldStartIdxoldEndIdx&&newStartIdx<=newEndIdx){if(oldStartVnode==null){//对于vnode.key比较,oldVnode=nulloldStartVnode=oldCh[++oldStartIdx]}elseif(oldEndVnode==null){oldEndVnode=oldCh[--oldEndIdx]}elseif(newStartVnode==null){新StartVnode=newCh[++newStartIdx]}elseif(newEndVnode==null){newEndVnode=newCh[--newEndIdx]}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)api.insertBefore(parentElm,oldStartVnode.el,api.nextSibling(oldEndVnode.el))oldStartVnode=oldCh[++oldStartIdx]newEndVnode=newCh[--newEndIdx]}else(sameVnode(oldEndVnode,newStartVnode)){patchVnode(oldEndVnode,newStartVnode)api.insertBefore(parentElm,oldEndVnode.el,oldStartVnode.el)oldEndVnode=oldCh[--oldEndIdx]newStartVnode=newCh[++newStartIdx]}else{//使用密钥时的比较if(oldKeyToIdx===undefined){oldKeyToIdx=createKeyToOldIdx(oldCh,oldStartIdx,oldEndIdx)//有密钥生成索引表}idxInOld=oldKeyToIdx[newStartVnode.key]if(!idxInOld){api.insertBefore(parentElm,createEle(newStartVnode).el,oldStartVnode.el)newStartVnode=newCh[++newStartIdx]}else{elmToMove=oldCh[idxInOld]if(elmToMove.sel!==newStartVnode.sel){api.insertBefore(parentElm,createEle(newStartVnode).el,oldStartVnode.el)}else{patchVnode(elmToMove,newStartVnode)oldCh[idxInOld]=nullapi.insertBefore(parentElm,elmToMove.el,oldStartVnode.el)}newStartVnode=newCh[++newStartIdx]}}}if(oldStartIdx>oldEndIdx){之前=newCh[newEndIdx+1]==空?null:newCh[newEndIdx+1].eladdVnodes(parentElm,before,newCh,newStartIdx,newEndIdx)}elseif(newStartIdx>newEndIdx){removeVnodes(parentElm,oldCh,oldStartIdx,oldEndIdx)}}一般来说就是创建4个指针分别指向新旧节点的头尾,然后两个两个比较,看是否是同一类型的节点再分情况。如果新老头节点是同一类型节点,直接调用patchVnode即是,同理,新老尾节点是同一类型节点,同样由patchVnode处理。但是如果判断新的head和旧的tail是同一类型的节点,除了patchVnode处理之外,还需要将tail节点移到head上。同样,如果新的尾巴和旧的头是同一类型的节点。patchVnode处理后,将头节点移到尾。如果四次比较都没有命中,则判断新头的节点是否在还没有比较过的旧节点中。如果是,patchVnode处理,并将老节点移动到dom中新head的位置,原老节点对应的Vnode为空,这样会跳过下一次判断,如果新节点不在旧节点列表,直接新建一个节点插入即可最后,当头指针和尾指针相遇未命中时,判断当前四个指针的位置,如果还有节点,这些节点是多余的,需要去掉。如果新的head和tail指针中间有节点,那么这些节点就是新加入的,需要插入到React中的diff就是一个先比较再更新的过程。在比较阶段,会先记录差异functiondiff(oldTree,newTree){//当前节点的标志,每遍历一个节点,就加1varindex=0varpatches={}//用于记录每个节点的差异ObjectdfsWalk(oldTree,newTree,index,patches)returnpatches}//两棵树的深度优先遍历functiondfsWalk(oldNode,newNode,index,patches){//比较oldNode和newNode的差异,记录下来,省略代码...patches[index]=[...]diffChildren(oldNode.children,newNode.children,index,patches)}先比较当前节点,记录差异,然后比较childnodes子节点的比较直接从左节点开始,判断是否在旧节点列表中,如果不在则记录新的,如果在则标记移动,最后根据差异更新dom。Vue根据key和sel认为两个节点元素是同一个元素。如果元素类型相同,但是classname不一致,会被认为是不同的元素,而react是根据元素类型和key来判断的,所以会被认为是相同的元素类型,而直接在上面的属性增减是不一样的。diff策略不同。vue比较的是新旧head和tail这四个节点,react是从左到右比较的。另外,react16提出了fiber的概念,diff是基于fiber树的,可以打断,让浏览器可以处理更高优先级的任务,而vue的diff和patch是不能打断的。vue和react的关键功能是一样的,就是告诉引擎当前比较的两个节点是否是同一个节点。当vue没有key,都是undefined的时候,那么vue会认为是同一个节点,就地重用这个元素,但是很可能这是一个input元素。您在其中输入了一些信息,更新不会移动这些信息,导致最最终的结果可能不是你所期望的,所以你需要指定key来防止元素被重用。React默认会将值赋值给key,所以子元素中新旧节点的位置可能发生了移动,但是因为react默认以index为key,所以它仍然会认为位于的元素同一个位置是同一个元素,根据它修改属性,导致和vue一样的问题
