DOM DIFF
通过JS层面的计算(逐层比对虚拟DOM对象),生成patch对象(即补丁对象),然后对补丁进行解析和重新渲染
可以处理同级节点顺序的改变
- 每一层级进行对比的计算法则:深度优先遍历
- 节点类型相同,看属性是否相同,如果不同,则产生属性的补丁包
{type:’ATTRS’,attrs:{class:’BB’}}
- 新的DOM节点不存在,被删除了
{type:’REMOVE’,index:xxx}
- 节点类型不相同,直接替换即可
{type:’REPLACE’,newNode:xxx}
- 文本的内容进行变化
{type:’TEXT’,text:xxx}
…
function diff(oldTree,newTree){
let patchs = {}; //=>补丁包 按照层级放置补丁包 {1:[],2:[]}
let index = 0; //=>比较的层级
//=>递归树,比较后的结果放到补丁包
walk(oldTree,newTree,index,patchs);
return patchs;
}
function walk(oldNode,newNode,index,patchs){
let currentPatch={};
if(!newNode){
//=>新元素不存在,代表删除
currentPatch.push({
type:'EMOVE',
index
});
}else if(typeof oldNode==="string"&&typeof newNode==="string"){
//=>如果是文本,判断文本是否改变
if(oldNode!==newNode){
currentPatch.push({
type:'TEXT',
text:newNode
});
}
}else if(oldNode.type===newNode.type){
//=>比较属性是否有更改
let attrs=diffAttr(oldNode.props,newNode.props);,
if(Object.keys(attrs).length>0){
currentPatch.push({
type:'ATTRS',
attrs
});
}
//=>如果有儿子节点,则遍历儿子
diffChildren(
oldNode.props.childrren,
newNode.props.childrren,
index,patchs);
}else{
//=>节点被替换了
currentPatch.push({
type:'REPACE',
newNode
});
}
//=>当前本级查找,确实有补丁,我们放到最外层补丁包中
if(currentPatch.length>0){
patchs[index]=currentPatch;
}
}
//=>比较儿子
function diffChildren(oldChildren,newChildren,index,patchs){
oldChildrren.forEach((child,ind)=>{
walk(child,newChildren[ind],++index,patchs);
})
}
//=>比较属性,生成补丁包
function diffAttr(oldAttrs,newAttrs){
let patch={};
for(ley key in oldAttrs){
//=>属性不一样(可能是把老的中某个删除了,这样获取的结果可能是undefined)
if(oldAttrs[key]!==newAttrs[key]){
patch[key]=newAttrs[key];
}
}
for(ley key in newAttrs){
//=>看老的节点中是否有这样一个属性(没有就是新增)
if(!oldAttrs.hasOwnProperty(key)){
patch[key]=newAttrs[key];
}
}
return patch;
}
根据补丁重新渲染
let patchs=diff(xxx,xxx); //=>两个虚拟DOM
let node;
let index=0;
walk(node);
functon walk(node){
let currentPatch=patches[index++];
let cildNodes=node.childNodes;
cildNodes.forEach(child=>walk(child));
if(currentPatch.length>0){
doPatch(node,currentPatch);
}
}
function doPatch(node,patch){
patchs.forRach((item,index)=>{
switch(patch.type){
case 'ATTRS':
for(let key in patch.attrs){
let val=patch.attrs[key];
if(val){
setAttr(node,key,val);
}else{
node.removeAttribute(key)
}
}
break;
case 'TEXT':
node.textContent=patch.text;
break;
case 'REPLACE':
let newNode=(patch.newNode instanceof Element)?render(patch.newNode):document.createTextNode(patch.newNode);
node.parentNode.replaceChild(newNode,node);
break;
case 'REMOVE':
node.parentNode.removeChild(node);
break;
}
})
}
总结
- 通过JS层面计算 === 对比的是虚拟DOM对象
- 第一次加载页面,所有的内容都要重新渲染(语法解析 -> 虚拟DOM -> 真实DOM -> 浏览器渲染) =>第一次加载页面越少渲染越好
- 上一次计算出来的虚拟DOM对象会存储起来,当状态或者其它数据改变,重新渲染组件(重新生成一套虚拟DOM对象)
- 把重新生成的虚拟DOM 和 之前存储起来的虚拟 DOM进行对比 =>把不一样的以补丁的形式存储起来(存储的还是对象)
- 重新渲染的过程,只是把补丁渲染到页面中,原有渲染过但是没有改变的东西是不需要处理的