vue patch 中节点移动由 diff 精准驱动,核心是用 insertbefore 复用并重排已有 dom 节点,前提是新旧 vnode 类型一致、key 相同且顺序变化;插入则是创建新节点挂载,二者均以最少 dom 操作保全状态。

Vue 的 Patch 过程中,节点移动和插入不是随意发生的操作,而是由虚拟 DOM 差异比对(Diff)精准驱动的结果。核心目标很明确:用最少的 DOM 操作完成视图更新,同时保留节点内部状态(比如 input 光标、视频播放进度、事件监听器等)。
节点移动只发生在“可复用且顺序变化”时
移动不是简单地把 DOM 元素拖来拖去,而是调用原生 insertBefore 把已有真实节点从旧位置剪切,插到新位置。但前提是:
- 新旧 VNode 类型一致(例如都是
div或同名组件) - 具有相同 key,被 Diff 算法识别为同一逻辑节点
- 它在新子节点列表中的索引位置发生了偏移(比如原本在第 3 位,现在要排到第 1 位)
- 父容器没变——跨父级的“移动”实际是卸载 + 挂载,不走 insertBefore
insertBefore 的实际触发场景
Vue 的双端 Diff 在比对子节点时,会按顺序尝试四种匹配模式。当出现“头尾”或“尾头”匹配时,就说明节点需要物理移动:
- 新头 ≡ 旧尾 → 把旧尾节点 DOM 插入到旧头节点 DOM 前面
-
新尾 ≡ 旧头 → 把旧头节点 DOM 插入到旧尾节点 DOM 后面(即
insertBefore(elm, null),等效于 appendChild) - refElm(插入参考位置)可能为
null,此时 insertBefore 自动追加到末尾
插入与移动的本质区别
插入是创建新节点并挂载;移动是复用旧节点并重排。二者都依赖父容器的 insertBefore,但来源不同:
-
插入:新 VNode 在旧列表中找不到对应 key,于是调用
createElm创建真实 DOM,再插入到指定位置 - 移动:旧节点 DOM 已存在,只需改变其在父容器内的兄弟顺序,不触发创建/销毁生命周期
- 两者都确保最终 DOM 结构与新 VNode 的 children 顺序完全一致
为什么不用 remove + append?
因为那样会丢失状态。比如一个带焦点的 <input>,remove 后再 append,光标就没了;视频元素会重置播放时间;自定义组件实例的内部数据可能被清空。而 insertBefore 是原地迁移,浏览器保持所有内部状态不变——这才是 Vue 保全用户体验的关键细节。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










