Vue移动节点的本质是复用+重定位,通过LIS算法最小化DOM操作,key决定复用精度,批量提交避免多次重排。

“移动节点的代价”不是指 DOM 元素物理上被拖来拖去,而是指浏览器执行 appendChild、insertBefore 或 removeChild 等操作时引发的真实开销——它会触发重排(reflow)、重绘(repaint),打断渲染流水线,还可能中断输入框焦点、动画状态或第三方组件生命周期。Vue 的核心策略不是“避免移动”,而是“让移动尽可能少、尽可能准、尽可能合并”。
移动的本质:复用 + 重定位,而非搬运
Vue 从不直接调用 node.moveTo()(DOM 标准里根本没这方法)。所谓“移动”,是两步合成效果:
- 把已有 DOM 节点从原父容器中 移除(但不销毁)
- 再把它 插入到新位置(可能是同一父容器内不同索引,也可能是新父节点)
关键在于:只要 key 和节点类型匹配,这个节点的 DOM 实例、事件监听器、内部状态(比如 input 的 value、focus 状态)都会保留。真正消耗性能的,是移除和插入这两步本身,以及它们引发的布局计算。
Vue3 的 LIS 优化:只动“必须动”的那几个
Vue 2 用双端比较快速处理头尾一致的情况,但面对乱序(如 [A,B,C,D] → [D,A,C,B])容易误判、多移动。Vue 3 引入最长递增子序列(LIS)算法,精准锁定最多可“原地不动”的节点集合:
- 先构建映射数组:对新列表每一项,查它在旧列表中的索引,得到类似
[3,0,2,1] - 对该数组求 LIS(如
[0,2]或[0,1]),这些索引对应的就是“相对顺序未变、可跳过移动”的节点 - 其余节点才被标记为需移动——数量被压缩到理论最小值
例如旧序 A(0) B(1) C(2) D(3),新序为 D A C B → 映射为 [3,0,2,1],其 LIS 是 [0,2](即 A 和 C),那么只需移动 D 和 B,而不是逐个调整四个位置。
key 不只是“建议”,它是移动决策的开关
没有正确 key,LIS 和双端比较都失效。Vue 只能退化为按索引比对:
-
:key="index":列表增删时 index 大量漂移,导致本该复用的节点被判定为“新节点”,触发重建(input 清空、动画重播) -
:key="item.id"(稳定唯一):Vue 才能跨更新识别“这是同一个实体”,哪怕它从第 2 位挪到了第 5 位,也只做一次 insertBefore
真实项目中,一个错误的 key 往往让“移动”变成“销毁+新建+重新绑定事件”,代价翻 3 倍以上。
批量与缓冲:把多次移动压成一次 DOM 提交
即使需要移动多个节点,Vue 也不会逐个调用 insertBefore:
- 收集所有待移动节点,统一用
DocumentFragment中转,避免中间态触发重排 - 最终只做一次父容器的
appendChild(fragment)或按目标顺序集中插入 - 对连续块(如 [X,A,B,C,Y] → [X,M,N,B,C,Y])自动跳过中间 BC,只处理首尾边界
你在 DevTools 里看到的 DOM 变化是原子性的,背后是几十次虚拟操作被压缩成极少数真实调用。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











