虚拟 DOM 的 Diff 算法:Vue/React 如何实现高效更新

为什么你的 Vue 组件在数据变化时能瞬间更新,而不需要刷新整个页面? 这背后藏着一个精妙的算法------Diff 算法。它通过对比新旧两棵虚拟 DOM 树,找出最小的变更集,从而实现高性能的视图更新。

今天,我们不谈复杂的源码,直接用 JavaScript 手写一个极简版的 Diff 算法,带你看透前端框架的核心秘密。

1. 为什么要用虚拟 DOM?

直接操作真实 DOM 是非常昂贵的。每次修改 DOM,浏览器都要重新计算样式、布局(Reflow)和绘制(Repaint)。

虚拟 DOM (Virtual DOM) 本质上就是一个普通的 JavaScript 对象:

css 复制代码
const vNode = {
    tag: 'div',
    props: { id: 'app' },
    children: [
        { tag: 'p', children: ['Hello'] }
    ]
};

通过对比两个 JS 对象(Diff),我们可以精确知道哪些地方变了,然后只更新那些变化的 DOM 节点。

2. Diff 算法的核心策略

完全的树对比复杂度是 O(n^3),这在大型应用中是不可接受的。Vue 和 React 都采用了以下优化策略,将复杂度降到了 O(n) :

  1. 同层比较:只比较同一层级的节点,不跨层级移动。
  2. Key 的作用 :给列表项加上唯一的 key,帮助算法识别节点身份,避免不必要的重排。
  3. 双指针优化:在处理列表时,从头部和尾部同时向中间逼近。

3. 手写极简版 Diff 算法

我们来实现一个最基础的对比逻辑:

scss 复制代码
function diff(oldVNode, newVNode) {
    // 1. 如果标签不同,直接替换整个节点
    if (oldVNode.tag !== newVNode.tag) {
        return { type: 'REPLACE', node: newVNode };
    }

    // 2. 标签相同,对比属性
    const propsPatch = diffProps(oldVNode.props, newVNode.props);

    // 3. 递归对比子节点
    const childrenPatch = diffChildren(oldVNode.children, newVNode.children);

    return {
        type: 'UPDATE',
        props: propsPatch,
        children: childrenPatch
    };
}

属性对比

属性对比比较简单,遍历新节点的属性,看是否有变化:

vbnet 复制代码
function diffProps(oldProps, newProps) {
    const patches = [];
    for (let key in newProps) {
        if (newProps[key] !== oldProps[key]) {
            patches.push({ key, value: newProps[key] });
        }
    }
    return patches;
}

子节点对比

这是最复杂的部分。为了简化,我们这里采用"按索引对比"的策略:

php 复制代码
function diffChildren(oldChildren, newChildren) {
    const patches = [];
    const len = Math.max(oldChildren.length, newChildren.length);

    for (let i = 0; i < len; i++) {
        if (i >= oldChildren.length) {
            // 新增节点
            patches.push({ type: 'ADD', index: i, node: newChildren[i] });
        } else if (i >= newChildren.length) {
            // 删除节点
            patches.push({ type: 'REMOVE', index: i });
        } else {
            // 递归对比
            patches.push(diff(oldChildren[i], newChildren[i]));
        }
    }
    return patches;
}

4. 工业界实战:Key 的重要性

在上面的简化版中,如果我们在列表头部插入一个元素,所有的后续元素都会被判定为"修改"。

加入 Key 之后: 算法会通过 key 发现,原来的节点只是位置变了,内容没变。这样就能复用 DOM 节点,极大地提升了性能。

5. 总结与面试考点

特性 说明
核心目标 最小化 DOM 操作,提升渲染性能
时间复杂度 O(n),得益于同层比较策略
Key 的作用 唯一标识节点,辅助算法进行高效的节点复用

面试官常问:

  1. "为什么不建议用 Index 作为 Key?" (答:当列表发生增删或排序时,Index 会变化,导致算法误判,引发不必要的重渲染。)
  2. "Vue 2 和 Vue 3 的 Diff 算法有什么区别?" (答:Vue 3 引入了静态标记和最长递增子序列优化,进一步减少了比对次数。)

📢 欢迎关注我的公众号:Lee 的成长日记

💡 福利:关注公众号回复"算法",获取本教程的 PDF 完整版及 LeetCode 刷题清单。

如果你觉得这篇关于"前端底层原理"的文章对你有帮助,欢迎点赞收藏!🚀

相关推荐
Hilaku1 小时前
MCP 协议会成为前端 AI 工程的标准接口吗?
前端·javascript·程序员
RobinDevNotes4 小时前
从 Electron 到 GPUI,9 个跨平台桌面方案该怎么选
前端·javascript·rust·electron·openresty·前端开发工具
软件技术NINI4 小时前
基于SpringBoot和Vue的新能源汽车销售推荐系统
java·vue.js·spring boot·后端·汽车·毕业设计·毕设
程序员大阳4 小时前
HTML+CSS+JS对话式实战(6)-学生端查看活动功能开发
javascript·css·html·模块·查看学生
招风的黑耳6 小时前
SpringBoot+Vue+MySQL 民宿在线预定平台 完整可运行版(源码+文档)
vue.js·spring boot·mysql
子兮曰1 天前
Chrome 权限元素实战:144 与 151 的渐进增强写法
前端·javascript·chrome
强尼银手1 天前
我花一天做了个格斗游戏原型,判定全对,然后被「美术」劝退了
前端·javascript
总要学点什么1 天前
第98篇:三维特效-材质线效果——给一条线换十种材质,虚线的图案只填了一半
开发语言·前端·javascript·ecmascript·gis·webgl·材质
10share1 天前
基于大模型的 i18n 方案:把中文原文当 Key,轻松搞定多语言维护
前端·vue.js
OpsEye1 天前
混合云 AI 架构,如何统一做敏感内容拦截?
javascript·ai编程