Patch是Vue虚拟DOM更新的核心函数,它通过对比新旧VNode,精准执行最小化DOM操作:先判断节点是否可复用(sameVnode),再差异化更新属性、文本或子节点,子节点采用双端Diff+key映射实现O(n)高效更新。

Vue 的 Patch 函数是虚拟 DOM 更新的核心,它不直接操作真实 DOM,而是通过比对新旧 VNode(虚拟节点),精准地计算出最小变更并应用到真实 DOM 上。理解它的逻辑,关键不在代码行数,而在“对比—决策—更新”这个闭环。
什么是 Patch?它干了三件事
Patch(打补丁)本质是一个函数:接收旧 VNode 和新 VNode,递归对比差异,然后在真实 DOM 上执行插入、删除、移动、属性更新等操作。它不是重绘整个组件,而是只动“该动的地方”。
- 判断是否可复用节点:先看新旧节点是不是同一类型(sameVnode),比如标签名、key、是否为注释节点等都一致,才进入精细化比对;否则直接卸载旧节点、挂载新节点。
- 复用时做精细化更新:若可复用,就更新属性(class、style、事件监听器)、文本内容、子节点结构等,但不会无脑递归——只有当子节点需要更新时,才调用 patch 子节点。
- 处理子节点的四种典型场景:新旧子节点分别是空、单节点、多节点,Patch 内部会走不同分支(如双端对比、key 映射查找),确保移动和复用高效。
关键伪代码逻辑(精简版)
以下不是真实源码,而是剥离细节后体现主干流程的伪代码:
Element Plus Vue3 桌面组件库。在开发 Vue 3.x 页面时调用,使用 Element Plus 组件(如 el-button、el-input、el-form、el-table 等)。
function patch(oldVNode, newVNode) {
// 1. 若 oldVNode 不存在,直接挂载新节点
if (!oldVNode) {
return createElm(newVNode);
}
// 2. 若 newVNode 不存在,卸载旧节点
if (!newVNode) {
removeElm(oldVNode);
return;
}
// 3. 若新旧节点不可复用(比如 tag 不同、key 不同),替换节点
if (!sameVnode(oldVNode, newVNode)) {
const parent = oldVNode.el.parentNode;
const elm = createElm(newVNode);
parent.insertBefore(elm, oldVNode.el);
removeElm(oldVNode);
return;
}
// 4. 可复用:复用 DOM 元素,仅更新必要部分
const elm = newVNode.el = oldVNode.el;
// 更新静态属性(class/style/props/directives)
patchStaticProps(oldVNode, newVNode);
// 更新文本节点
if (isTextVNode(newVNode)) {
if (oldVNode.text !== newVNode.text) {
elm.textContent = newVNode.text;
}
}
// 更新元素节点的子节点
else {
patchChildren(oldVNode, newVNode);
}
}
function patchChildren(oldVNode, newVNode) {
const oldCh = oldVNode.children;
const newCh = newVNode.children;
if (Array.isArray(oldCh) && Array.isArray(newCh)) {
// 核心:双端 Diff(头-头、尾-尾、头-尾、尾-头 + key 映射)
updateChildren(oldCh, newCh, oldVNode.el);
} else if (Array.isArray(newCh)) {
// 旧无子,新有子 → 全部新增
newCh.forEach(child => insert(createElm(child), oldVNode.el));
} else if (Array.isArray(oldCh)) {
// 旧有子,新无子 → 全部卸载
oldCh.forEach(child => removeElm(child));
}
}
你真正该记住的三个设计意图
- key 不是可选,是 Diff 的锚点:没有 key 时,Vue 默认用“就地复用”策略(index 一致就复用),容易导致状态错乱;加了 key,Patch 才能按唯一标识精准映射节点,保障更新语义正确。
- 子节点 diff 是性能关键路径:updateChildren 中的双端对比 + key map 查找,让列表增删移动的复杂度控制在 O(n),而非暴力遍历 O(n²)。
- Patch 是同步的,但触发时机受响应式系统约束:数据变化 → 触发 setter → 收集依赖 → 异步推入 nextTick 队列 → 在下一个 microtask 中执行 patch。所以你看到的“响应式更新”,其实是响应式 + Patch + 异步调度共同完成的。
不复杂但容易忽略:Patch 本身不关心数据怎么变,它只忠实地执行“新旧 VNode 对比结果”。而 VNode 怎么来?靠 render 函数或模板编译生成——这才是 Vue 响应式更新链条的起点。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










