vue中diff算法
本质:找出两个对象之间的差异
核心:子节点数组进行比较(首尾两端对比)
作用
渲染真实的DOM的开销很大,如果修改了某个数据直接渲染到真实DOM上会引起整个DOM树的重绘和重排。diff算法旨在只更新修改的一小块DOM,而不是整个DOM。
找出两个对象之间的差异,目的是尽可能的做到节点之间的复用
1.真实DOM和虚拟DOM
当数据发生改变时: 根据真实DOM生成一颗 virtual DOM ,当 virtual DOM 某个节点的数据改变后会生成一个新的 Vnode ,然后 Vnode 和 oldVnode 作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使 oldVnode 的值为 Vnode 。
例如下方是一个真实dom节点
<div>
<p class="node">123</p>
</div>
可见,一个dom节点主要包含自身标签名、自身的属性、子节点三部分。
对应的virtual DOM:
var Vnode = {
tag: 'div',
children: [
{ tag: 'p', text: '123', attr:{class:'node'} }
]
};
virtual DOM是将真实的DOM的数据抽取出来,以对象的形式模拟树形结构。
2.diff的比较方式
diff在新旧节点比较时,只会同层级进行比较,不会跨级比较。例如:
<div>
<p>123</p>
</div>
<div>
<span>456</span>
</div>
上方代码会分别比较同层级的两个div和第二层的p和span标签,但是不会讲div和span相比较。在其他地方看到的一张很形象的图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/2bcd012cb9d64ce48356cd9cdf8c0ed8.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP5bym55qE5Y2a5a6i,size_19,color_FFFFFF,t_70,g_se,x_16)
diff涉及到的函数
//1.判断两棵Virtual Dom Tree 差异
//2.把差异更新到真实Dom中去
let patchs = diff(oldTree, newTree);//获取两棵Virtual Dom Tree 差异
patch(ulDom, patchs);//找到对应的真实dom,进行部分渲染
diff函数调用流程:
![在这里插入图片描述](https://img-blog.csdnimg.cn/745bc7dcfc4e4867a5b23c325080a7a7.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5bCP5bym55qE5Y2a5a6i,size_20,color_FFFFFF,t_70,g_se,x_16)
//深度遍历树,将需要做变更操作的取出来
//局部更新 DOM
function patch(node,patchs){
//代码略
}
// diff 入口,比较新旧两棵树的差异
function diff (oldTree, newTree) {
let index = 0
let patches = {} // 记录每个节点差异的补丁
dfs(oldTree, newTree, index, patches)
return patches;
}
/**
* dfs 深度遍历查找节点差异
* @param oldNode - 旧虚拟Dom树
* @param newNode - 新虚拟Dom树
* @param index - 当前所在树的第几层
* @param patches - 记录节点差异
*/
function dfs (oldNode, newNode, index, patches){
let currentPatch = [];//当前层的差异对比
if (!newNode) {
//如果节点不存不用处理,listdiff函数会处理被删除的节点
}else if (isTxt(oldNode) && isTxt(newNode)) {//isTxt用来判断是否是文本,为了简便这边并没有声明
if (newNode !== oldNode)
currentPatch.push({ type: "text", content: newNode })
//如果发现文本不同,currentPatch会记录一个差异
}else if(oldNode.tagName === newNode.tagName && oldNode.key === newNode.key){
//如果发现两个节点一样 则去判断节点是属性是否一样,并记录下来
let attrsPatches = diffAttrs(oldNode, newNode)
if (attrsPatches) {//有属性差异则把差异记录下来
currentPatch.push({ type: "attrs", "attrs": attrsPatches })
}
// 递归遍历子节点,并对子节点进行diff比较
diffChildren(oldNode.children, newNode.children, index, patches)
}else{
//最后一种情况是,两个节点完全不一样,这样只需要把旧节点之间替换就行
//把当前差异记录下来
currentPatch.push({ type: "replace", node: newNode})
}
//如果有差异则记录到当前层去
if (currentPatch.length) {
if (patches[index]) {
patches[index] = patches[index].concat(currentPatch)
} else {
patches[index] = currentPatch
}
}
}
//判断两个节点的属性差异
function diffAttrs(oldNode, newNode){
let attrsPatches = {};//记录差异
let count = 0;//记录差异的条数
/**
代码略
判断两个节点的属性差异的代码就略了,
让你们知道这里的代码就是判断两个节点的属性有哪些差异,
如果有差异就记录在attrsPatches这个对象中,并把它返回
**/
if(0 == count){
return null;
}else {
return attrsPatches;
}
}
//判断孩子节点
function diffChildren(oldChild, newChild, index, patches){
let { changes, list } = listDiff(oldChild, newChild, index, patches);
if (changes.length) {//如果有差异则记录到当前层去
if (patches[index]) {
patches[index] = patches[index].concat(changes);
} else {
patches[index] = changes;
}
}
// 代码略
//遍历当前数组
oldChild && oldChild.forEach((item, i) => {
// 代码略
let node;// 经过判断后node节点是同时存在于oldChild 和 newChild中
//则对节点进行递归遍历 相当于 进入下一层 节点,
let curIndex;
dfs(item, node, curIndex, patches);
// 代码略
})
}
//判断oldNodeList, newNodeList 节点的位置差,主要是为了判断哪些节点被移动、删除、新增。
function listDiff(oldNodeList, newNodeList, index){
let changes = [];//记录 oldNodeList, newNodeList节点的位置差异,是被移动、删除、新增
let list = [];//记录 oldNodeList,newNodeList 同时存在的节点
/**
具体判断逻辑的代码就略了
**/
return {changes,list};
}
总结
Virtual Dom 算法的实现主要是以下三步
通过 JS 来模拟生成 Virtual Dom Tree
判断两个 Tree 的差异
渲染差异