为啥突然写diff算法
整天背八股文,背React对老diff算法的三种优化,突然心血来潮想要不自己去试试看diff算法,本质上还是一个深度优先遍历,说干就干查了一些资料和视频之后我就开始动手写了(下文中部分代码借鉴了·codeXiu·大佬)。
虚拟dom
首先创建两个虚拟Dom树
let newTree = createElement("div", {class: "a"}, [
createElement("div", {class: "b"}, ["dlz"])
]);
let oldTree = createElement("div", {class: "a"}, [
createElement("div", {class: "d"}, ["ccc"])
]);
//文本不同,子节点不同
前端大哥都知道虚拟Dom本质是是一个js对象里面包含了很多属性,这些属性经过对比生成之后变成外我们熟知的真实Dom。所以实现一个diff算法需要比较以下几步:
- 文本的不同:TEXT
- 属性的不同:ATT
- 标签的不同:直接替换
- 删除:delete
- 替换:repalce
实现diff算法具体过程
首先我们肯定有个补丁对象用来存放不同对象的,还有index用来给每个虚拟Dom的标记
let patch = { }
let index = 0
1.比较两个虚拟Dom树的差异
//主函数
function myDiff(oldTree, newTree) {
//walk是遍历函数
walk(oldTree, newTree, index);
}
2.比较文本的不同
// 数据描述: {type: 不同的类型,不同的地方}
if((typeof oldNode === "string") && (typeof newNode === "string")){
// 如果文本内容不一样
if(newNode !== oldNode){
patch.push({type: TEXT, text: newNode});
// patch是补丁对象
}
}
3.比较属性的不同
// 如果类型相同就比较属性, 类型不相同默认换掉了整个元素
//type就是标签名,只有标签相同才能继续比较属性
if(oldNode.type === newNode.type){
// 遍历新老节点属性的不同
//props是属性
let attr = diffAttr(oldNode.props, newNode.props);
// 如果有不同, 就加入patch中
if (Object.keys(attr).length > 0) {
patch.push({ type: ATTR, attr });
}
// 遍历子节点
diffChildren(oldNode.children, newNode.children);
}
4.实现diffAttr()遍历属性是否相同
function diffAttr(oldAttr, newAttr){
let attr = {};
// 看两个属性是否不同(修改)
for (key in oldAttr) {
if(oldAttr[key] != newAttr[key]){
attr[key] = newAttr[key];
}
}
// 是否新增
for (key in newAttr) {
if(!oldAttr.hasOwnProperty(key)){
attr[key] = newAttr[key];
}
}
//返回attr对象
return attr;
}
5.实现diffChildren()遍历子节点上面的属性是否相同
function diffChildren(oldChildren, newChildren){
oldChildren.forEach(function(child, i){
// 子节点递归遍历属性
walk(child, newChildren[i], ++ index);
});
}
6.删除
// 如果没有新节点,说明删除了,标记处删除的索引
if(!newNode){
patch.push({type: REMOVE, index});
}
7.替换
// 其余情况为替换
patch.push({type: REPLACE, newNode});
整体代码
let patches = {};
let index = 0;
const ATTR;
const TEXT;
const REMOVE;
const REPLACE;
function diff(oldTree, newTree){
walk(oldTree, newTree, index);
}
function walk(oldNode, newNode, index){
let patch = [];
// 删除
if(!newNode){
patch.push({type: REMOVE, index});
// 文本
}else if((typeof oldNode === "string") && (typeof newNode === "string")){
if(newNode !== oldNode){
patch.push({type: TEXT, text: newNode});
}
}else if(oldNode.type === newNode.type){
// 属性
let attr = diffAttr(oldNode.props, newNode.props);
if (Object.keys(attr).length > 0) {
patch.push({ type: ATTR, attr });
}
diffChildren(oldNode.children, newNode.children);
}else {
// 替换
patch.push({type: REPLACE, newNode});
}
if(patch.length > 0){
patches[index] = patch;
}
}
// 比较属性的不同
function diffAttr(oldAttr, newAttr){
let attr = {};
// 看两个属性是否不同(修改)
for (key in oldAttr) {
if(oldAttr[key] != newAttr[key]){
attr[key] = newAttr[key];
}
}
// 是否新增
for (key in newAttr) {
if(!oldAttr.hasOwnProperty(key)){
attr[key] = newAttr[key];
}
}
return attr;
}
// 比较子节点的属性
function diffChildren(oldChildren, newChildren){
oldChildren.forEach(function(child, i){
walk(child, newChildren[i], ++ index);
});
}
之后的patch对象里面就包含两棵树之间的差异了