聊天讨论 前端开发:关于 diff 算法详解

coderwamgh(wang) · 2025年12月25日 · 14 次阅读

# 前言

前端开发中,关于 JS 原生的内容和前端算法相关的内容一直都是前端工作中的核心,不管是在实际的前端业务开发还是前端求职面试,都是非常重要且必备的内容。那么本篇博文来分享一个关于前端开发中必备内容:diff 算法,diff 算法在前端实战中和前端求职面试中都是必备知识,整理总结一下,方便查阅使用。

#diff 算法是什么?

diff 算法其实就是用于比较新旧虚拟 DOM 节点的差异性的算法。众所周知,每一个虚拟 DOM 节点都会有一个唯一标识,即 Key,diff 算法把树形结构按照层级分解,只比较同级元素,不同层级的节点只有创建和删除操作,通过对比新旧节点的 Key 来判断当前节点是否改变,把两个节点不同的地方存储在 patch 对象中,然后利用 patch 记录的消息进行局部更新 DOM 操作。

注意:若输入的新节点不是虚拟 DOM , 那么需要将 DOM 节点转换为虚拟 DOM 才行,也就是说 diff 算法是针对虚拟 DOM 的。

#patch() 函数

想必大家都知道,前端领域中在之前传统的 DOM 操作非常昂贵,数据的改变往往需要更新 DOM 树上的多个节点,可谓是牵一发而动全身,所以虚拟 DOM 和 Diff 算法的诞生就是为了解决上述问题。

前端的 Web 界面由 DOM 树来构成,当某一部分发生变化的时候,其实就是对应的某个 DOM 节点发生了变化。在 Vue 中,构建 UI 界面的思路是由当前状态决定界面,前后两个状态就对应两套界面,然后由 Vue 来比较两个界面的区别,本质是比较 DOM 节点差异当两个节点不同时应该如何处理,分为两种情况:一、节点类型不同;二、节点类型相同,但是属性不同。了解它们就需要对 DOM 树进行 Diff 算法分析。

#diff 算法的优势

diff 算法的性能优势在于对比新旧两个 DOM 节点的不同的时候,只对比同一级别的 DOM 节点,一旦发现有不同的地方,后续的 DOM 子节点将被删掉而不再作对比操作。使用 diff 算法提高了更新 DOM 的性能,不用再把整个页面全部删除后重新渲染;使用 diff 算法让虚拟 DOM 只包括必须的属性,不再把真实 DOM 的全部属性都拿出来。

#diff 算法的示例

这里先来以 Vue 来介绍一下 diff 算法的示例,这里直接在 vue 文件的模板中进行一个简单的标签实现,需要被 vue 处理成虚拟 DOM,然后渲染到真实 DOM 中,具体代码如下所示:

//标签设置
 
//相对应的虚拟DOM结构
 
const dom = {
 
type: 'div',
 
attributes: [{id: 'content'}],
 
children: {
 
type: 'p',
 
attributes: [{class: 'sonP'}],
 
text: 'Hello'
 
}
 
}

通过上面的代码演示可以看到,新建标签之后,系统内存中会生成对应的虚拟 DOM 结构,由于真实 DOM 属性有很多,无法快速定位是哪个属性发生改变,然后通过 diff 算法能够快速找到发生变化的地方,然后只更新发生变化的部分渲染到页面上,也叫打补丁。

# 虚拟 DOM

虚拟 DOM 是保存在程序内存中的,它只记录 DOM 的关键信息,然后结合 diff 算法来提高 DOM 更新的性能操作,在程序内存中比较差异,最后给真实 DOM 打补丁更新操作。

diff 算法的比较规则

diff 算法在进行比较操作的规则是这样的:

1. 新节点前和旧节点前;

2. 新节点后和旧节点后;

3. 新节点后和旧节点前;

4. 新节点前和旧节点后。

只要符合一种情况就不会再进行判断,若没有符合的,就需要循环来寻找,移动到旧前之前操作。结束查找的前提是:旧节点前<旧节点后 或者 新节点后>新节点前。

机 - 会

技术大厂,前端 - 后端 - 测试,新一线和一二线城市等地均有机 - 会,感兴趣可以试试。待遇和稳定性都还不错~

diff 算法的三种比较方式

diff 算法的比较方式有三种,分别如下所示:

方式一:根元素发生改变,直接删除重建

也就是同级比较,根元素发生改变,整个 DOM 树会被删除重建。如下示例:

ini 体验AI代码助手 代码解读复制代码//旧的虚拟DOM
<ul id="content">
<li class="sonP">hello</li>
</ul>
//新的虚拟DOM
<div id="content">
<p class="sonP">hello</p>
</div>

方式二:根元素不变,属性改变,元素复用,更新属性

这种方式就是在同级比较的时候,根元素不变,但是属性改变之后更新属性,示例如下所示:

//旧的虚拟DOM
<div id="content">
<p class="sonP">hello</p>
</div>
//新的虚拟DOM
<div id="content" title="hello">
<p class="sonP">hello</p>
</div>

###方式三:根元素不变,子元素不变,元素内容发生变化

也就是根元素和子元素都不变,只是内容发生改变,这里涉及到三种小的情况:无 Key 直接更新、有 Key 但以索引为值、有 Key 但以 id 为值。

####1、无 Key 直接更新

无 Key 直接就地更新,由于 v-for 不会移动 DOM,所以只是尝试复用,然后就地更新;若需要 v-for 来移动 DOM,则需要用特殊 attribute key 来提供一个排序提示。示例如下所示:

<ul id="content">
<li v-for="item in array">
{{ item }}
<input type="text">
</li>
</ul>
<button @click="addClick">在下标为1的位置新增一行</button>
export default {
data(){
return {
array: ["11", "44", "22", "33"]
}
},
methods: {
addClick(){
this.array.splice(1, 0, '44')
}
}
};

####2、有 Key 但以索引为值

这里也是直接就地更新,通过新旧虚拟 DOM 对比,key 存在就直接复用该标签更新的内容,若 key 不存在就直接新建一个。示例如下所示:

-
 
{{ item }}
 
在下标为1的位置新增一行
 
export default {
 
data(){
 
return {
 
array: ["11", "44", "22", "33"]
 
}
 
},
 
methods: {
 
addClick(){
 
this.array.splice(1, 0, '44')
 
}
 
}
 
};

通过上面代码可以看到,通过 v-for 循环产生新的 DOM 结构, 其中 key 是连续的, 与数据对应一致,然后比较新旧 DOM 结构, 通过 diff 算法找到差异区别, 接着打补丁到页面上,最后新增补一个 li,然后从第二元素以后都要更新内容。

####3、有 Key 但以 id 为值

由于 Key 的值只能是唯一不重复的,所以只能以字符串或数值来作为 key。由于 v-for 不会移动 DOM,所以只是尝试复用,然后就地更新;若需要 v-for 来移动 DOM,则需要用特殊 attribute key 来提供一个排序提示。

若新 DOM 数据的 key 存在, 然后去旧的虚拟 DOM 里找到对应的 key 标记的标签, 最后复用标签;若新 DOM 数据的 key 存在, 然后去旧的虚拟 DOM 里没有找到对应的 key 标签的标签,最后直接新建标签;若旧 DOM 结构的 key, 在新的 DOM 结构里不存在了, 则直接移除对应的 key 所在的标签。

<ul id="content">
<li v-for="object in array" :key="object.id">
{{ object.name }}
<input type="text">
</li>
</ul>
<button @click="addClick">在下标为1的位置新增一行</button>
export default {
data(){
return {
array: [{id:11,name:"11"}, {id:22,name:"22"}, {id:33,name:"33"}]
}
},
methods: {
addClick(){
this.array.splice(1, 0,{id:44,name: '44'})
}
}
};

# 最后

通过本文关于前端开发中关于 diff 算法的详细介绍,diff 算法不管是在实际的前端开发工作中还是在前端求职面试中都是非常关键的知识点,所以作为前端开发者来说必须要掌握它相关的内容,尤其是从事前端开发不久的开发者来说尤为重要,是一篇值得阅读的文章,重要性就不在赘述。欢迎关注,一起交流,共同进步。

——转载自:三掌柜

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请 注册新账号