实现一个基本功能的diff算法(包含注释讲解)

为啥突然写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对象里面就包含两棵树之间的差异了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值