解析vue框架中的diff算法
2020-09-24 本文已影响0人
祝家庄打烊
大致思路是这样的,真实DOM树和虚拟DOM树进行比较,差异的部分通过对象进行存储起来,生成一个patch补丁包。等到下一渲染的时候,对比补丁包的内容进行DOM操作。这样大大节省渲染速度,提高了性能。
创建两个对象,模拟DOM树
var createElement1 = common.createElement("div",{class:"vitual"},[
common.createElement("p",{class:"vitual-p"},["vitual"]),
common.createElement("p",{class:"vitual-p"},["vitual"]),
common.createElement("p",{class:"vitual-p"},["vitual"]),
common.createElement("p",{class:"vitual-p"},["vitual"]),
]);
var createElement2 = common.createElement("div",{class:"vitual",style:"font-size:16px"},[
common.createElement("p",{class:"vitual-p"},["vitual123"]),
common.createElement("p",{class:"vitual-p"},["vitual"]),
common.createElement("p",{class:"vitual-p"},["vitual"])
]);
返回对象的形式进行比较,type存放的元素名称,props存放的属性,children存放的子节点
createElement(type,props,children){
return {type,props,children}
}
渲染成真实的DOM
var docDom = common.render(createElement1);
创建元素,修改元素的属性,遍历子节点,子节点不是字符串,递归继续执行render,是则创建文本节点,输出返回
render(res){
var createDom = document.createElement(res.type);
Object.keys(res.props).forEach(key=>{
this.setAttr(createDom,key,res.props[key])
});
res.children && res.children.forEach(item=>{
var createEle = typeof(item)=="string"?document.createTextNode(item):this.render(item);
createDom.appendChild(createEle)
})
return createDom;
},
setAttr(dom,key,value){
var domName = dom.tagName.toUpperCase();
switch(key){
case "value":
if(domName=="INPUT" && domName=="TEXTAREA"){
dom.value = value;
}else{
dom.setAttribute(key,value);
};
break;
case "style":
dom.style.cssText = value;
break;
default:
dom.setAttribute(key,value);
}
return dom;
}
获取旧节点并渲染到body中
var docDom = common.render(createElement1);
el.appendChild(docDom);
diff算法,比较两个虚拟DOM的差异,也可以是虚拟DOM和真实DOM的差异
var diff = common.diff(createElement1,createElement2);
创建补丁包,补丁包的格式为 {0:[{type:"REMOVE",content:"value"}]}。新对象不存,说明DOM元素已经被删除。新老元素的类型为字符串,说明DOM是文本节点。新老对象类型相同,可能元素属性发现变化,进行下一步比较,遍历子节点比较,记录每个节点的索引。
patcher:{},
index:0,
diff(oldEl,newEl){
var patch = [];
if(!newEl){
patch.push({type:"REMOVE",content:this.index});
this.patcher[this.index] = patch;
}else if(typeof(oldEl)=="string" && typeof(newEl)=="string"){
if(oldEl!=newEl){
patch.push({type:"TEXT",content:newEl});
this.patcher[this.index] = patch;
}
}else if(oldEl.type==newEl.type){
var compareAttr = this.compareAttr(oldEl.props, newEl.props);
if(Object.keys(compareAttr).length>0){
patch.push({type:"ATTR",content:compareAttr});
this.patcher[this.index] = patch;
}
oldEl.children && oldEl.children.forEach((item,index)=>{
this.index++;
this.diff(item,newEl.children[index])
})
}else{
patch.push({type:"REPLACE",content:newEl});
this.patcher[this.index] = patch;
}
return this.patcher;
},
compareAttr(oldAttr,newAttr){
var reAttr = {};
Object.keys(oldAttr).forEach(key=>{
if(oldAttr[key]!=newAttr[key]){
reAttr[key] = newAttr[key]
}
});
Object.keys(newAttr).forEach(key=>{
if(!oldAttr.hasOwnProperty(key)){
reAttr[key] = newAttr[key]
}
});
return reAttr;
}
补丁包渲染到页面上
common.update(el,diff);
重新根据DOM节点记录索引,遍历子节点对比每个子节点在补丁包发生哪些变化,从而操作DOM元素
updateIdx:0,
update(el,patch){
let childNode = el.childNodes;
childNode && childNode.forEach(item=>{
this.loadDom(item,patch)
this.updateIdx++;
this.update(item,patch)
})
},
loadDom(dom,patch){
patch[this.updateIdx] && patch[this.updateIdx].forEach(item=>{
switch(item.type){
case "REMOVE":
dom.parentNode.removeChild(dom);
break;
case "TEXT":
dom.textContent = item.content;
break;
case "ATTR":
Object.keys(item.content).forEach(key=>{
if(item.content[key]){
this.setAttr(dom,key,item.content[key])
}else{
dom.removeAttribute(key);
}
})
break;
case "REPLACE":
let newDom = item.content.type?this.render(item.content):document.createTextNode(item.content);
dom.parentNode.replaceChild(newDom,dom);
break
}
})
}