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

解析2.0的diff算法

时间:2023-03-31 20:30:46 vue.js

概念diff算法的生成主要是直接操作dom,比较浪费性能。dom-diff可以在每次渲染时进行比较。例如,一个列表中有三个元素,过一段时间会生成一条新的数据。如果可以复用的元素直接复用,不操作相同的元素,diff算法的特点是层次比较dom-diff是对虚拟节点进行操作的,虚拟节点的属性很少(tag,data,children,key)realdom,每次操作realdom都会重新创建很多属性,极大的消耗了diff算法实现的性能。我们还了解到diff算法使用旧虚拟节点和新虚拟节点。节点是比较的,那么我们来看看比较规则。那么我们现在使用vue源码中的三个方法生成两个虚拟节点。//compileToFunction解析html生成render函数import{compileToFunction}from'./compiler/index.js';//patch虚拟节点对比//createElm创建真的是domimport{patch,createElm}from'./vdom/patch';//1.创建第一个虚拟节点letvm1=newVue({data:{name:'meng'}});letrender1=compileToFunction('

{{name}}}
')letoldVnode=render1.call(vm1)//2.创建第二个虚拟节点letvm2=newVue({data:{name:'li'}});letrender2=compileToFunction('

{{name}}

');letnewVnode=render2.call(vm2);//3.通过第一个虚拟节点进行第一次渲染letel=createElm(oldVnode)document.body.appendChild(el);//4.调用patch方法进行比较操作patch(oldVnode,newVnode);生成后的虚拟节点,如下图所示console.log(oldVnode,newVnode)1.标签比较如果标签不同,就用新的替换旧的if(oldVnode.tag!==vnode.tag){oldVnode.el.parentNode.replaceChild(createElm(vnode),oldVnode.el)}2.标签中的文本比较//如果标签不存在,则为文本如果文本不一致,让新的替换旧的内容同时复用旧标签,只有内容替换节省了创建标签的性能}}3。比较属性//重用标签,并更新属性让el=vnode.el=oldVnode.el;updateProperties(vnode,oldVnode.data);functionupdateProperties(vnode,oldProps={}){让newProps=vnode.data||{};让el=vnode.el;//比较样式letnewStyle=newProps.style||{};让oldStyle=oldProps.style||{};//如果新的属性不存在,直接清除旧的oldProps中的键){if(!newProps[key]){el.removeAttribute(key);}}for(letkeyinnewProps){if(key==='style'){for(letstyleNameinnewProps.style){el.style[styleName]=newProps.style[styleName];}}elseif(key==='class'){el.className=newProps.class;}else{el.setAttribute(key,newProps[key]);}}}4.子元素比较//比较子节点letoldChildren=oldVnode.children||[];让newChildren=vnode.children||[];//新旧都需要比较Childif(oldChildren.length>0&&newChildren.length>0){//老的有儿子,新的不清零}elseif(oldChildren.length>0){el.innerHTML='';//New有孩子}elseif(newChildren.length>0){for(leti=0;i{map[item.key]=index});返回地图;}letmap=makeIndexByKey(oldChildren);使用新元素在旧元素中查找,找到则移动,找不到则直接插入旧parent.insertBefore(createElm(newStartVnode),oldStartVnode.el);}else{//如果有移动操作letmoveVnode=oldChildren[moveIndex];oldChildren[moveIndex]=undefined;parent.insertBefore(moveVnode.el,oldStartVnode.el);patch(moveVnode,newStartVnode);}newStartVnode=newChildren[++newStartIndex]有剩余的直接删除if(oldStartIndex<=oldEndIndex){for(leti=oldStartIndex;i<=oldEndIndex;i++){letchild=oldChildren[i];if(child!=undefined){parent.removeChild(child.el)}}}更新操作Vue.prototype._update=function(vnode){constvm=this;constprevVnode=vm._vnode;//保留最后一个vnodevm._vnode=vnode;//如果是第一次渲染if(!prevVnode){vm.$el=patch(vm.$el,vnode);//需要创建一个带有虚拟节点的真实节点来替换真实的$el//我想通过虚拟节点来渲染真实的dom}else{//如果最后一次更新操作需要比较diffvm.$el=patch(prevVnode,vnode);//更新时的diff操作}}新的虚拟dom与旧的虚拟dom相比,是对真实的dom进行操作的,所以当旧的虚拟dom不存在时,插入新的dom不会改变原来的虚拟dom,所以我们是不在乱序比较中操作旧的虚拟dom指针