扫码
40.67M · 2026-04-17
为什么你的 Vue 组件在数据变化时能瞬间更新,而不需要刷新整个页面? 这背后藏着一个精妙的算法——Diff 算法。它通过对比新旧两棵虚拟 DOM 树,找出最小的变更集,从而实现高性能的视图更新。
今天,我们不谈复杂的源码,直接用 JavaScript 手写一个极简版的 Diff 算法,带你看透前端框架的核心秘密。
直接操作真实 DOM 是非常昂贵的。每次修改 DOM,浏览器都要重新计算样式、布局(Reflow)和绘制(Repaint)。
虚拟 DOM (Virtual DOM) 本质上就是一个普通的 JavaScript 对象:
const vNode = {
tag: 'div',
props: { id: 'app' },
children: [
{ tag: 'p', children: ['Hello'] }
]
};
通过对比两个 JS 对象(Diff),我们可以精确知道哪些地方变了,然后只更新那些变化的 DOM 节点。
完全的树对比复杂度是 O(n^3),这在大型应用中是不可接受的。Vue 和 React 都采用了以下优化策略,将复杂度降到了 O(n) :
key,帮助算法识别节点身份,避免不必要的重排。我们来实现一个最基础的对比逻辑:
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
};
}
属性对比比较简单,遍历新节点的属性,看是否有变化:
function diffProps(oldProps, newProps) {
const patches = [];
for (let key in newProps) {
if (newProps[key] !== oldProps[key]) {
patches.push({ key, value: newProps[key] });
}
}
return patches;
}
这是最复杂的部分。为了简化,我们这里采用“按索引对比”的策略:
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;
}
在上面的简化版中,如果我们在列表头部插入一个元素,所有的后续元素都会被判定为“修改”。
加入 Key 之后: 算法会通过 key 发现,原来的节点只是位置变了,内容没变。这样就能复用 DOM 节点,极大地提升了性能。
| 特性 | 说明 |
|---|---|
| 核心目标 | 最小化 DOM 操作,提升渲染性能 |
| 时间复杂度 | O(n),得益于同层比较策略 |
| Key 的作用 | 唯一标识节点,辅助算法进行高效的节点复用 |
面试官常问:
欢迎关注我的公众号:Lee 的成长日记
如果你觉得这篇关于“前端底层原理”的文章对你有帮助,欢迎点赞收藏!