想象你请了两支装修队来改造同一个房子。传统装修队的做法是:客户说客厅沙发换个颜色,他们直接抡起大锤把整栋楼拆了重建,砌墙、布线、刷漆从头来一遍——又贵又慢,邻居还投诉。后来市面上出现了两支聪明的装修队:他们手里拿的不是锤子,而是图纸。接到需求先改设计图,再对比新旧图纸找出哪些墙变了,最后只派工人改掉有变化的那几处。 这套“先画图、再对比、只改动差异”的流程,就是 React 和 Vue 共用的虚拟 DOM 技术。但两支队伍找差异的风格完全不同:React 装修队(简称 R 队)从进门开始,沿着走廊一间间按顺序排查;Vue 装修队(简称 V 队)则从走廊两头同时包抄,还给每个房间都贴了门牌号(key),一眼就知道哪间房该动。故事就从这里开始,带你拆解两大框架的虚拟 DOM 与 Diff 算法。
理解虚拟 DOM 与 Diff 的内核,你才能写出高渲染性能的 React/Vue 组件,框架原理面试不虚,性能调优时不再瞎改代码。
目录
可视化演示
动态演示React与Vue虚拟DOM的核心机制,通过总览/传输/转换/输出四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
React与Vue虚拟DOM:虚拟DO 虚拟DOM概念与JS对象描述渲 React Fiber架构与D 核心机制可视化 React与Vue虚拟DOM整体架构与关键组件
虚拟DOM原理——为什么先画图纸
老张的装修队以前干活,客户说“改一下玄关的鞋柜”,他们直接请来挖掘机把整栋房子推平重盖。这听起来荒唐,但浏览器处理真实 DOM 操作时就是这样:每改一次 DOM 节点,浏览器就要重新计算样式、重新布局、重新绘制,改的次数越多,页面卡得越厉害。频繁操作真实 DOM,就像频繁推倒重盖房子,每一次都代价高昂。
于是聪明的装修队不再直接动工,而是先在纸上画一份设计图。这份设计图就是虚拟 DOM(Virtual DOM):它不是真实的 HTML 标签,而是一棵由 JavaScript 普通对象组成的树。每个节点叫 vnode,记录着“这里有个 div、它有这些属性、下面挂着这些子节点”。改设计图只需要动笔,一行文字、一个对象,成本极低。
真实 DOM 操作贵在哪?贵在浏览器要经历一系列“重排重绘”流水线。而虚拟 DOM 把改动集中到 JS 对象层面,最后才一次性提交给浏览器,就像装修队先在图纸上反复推敲,确定方案后再进场施工,把几十次返工压缩成一次精准的施工。
所以装修队总结出一条经验:图纸怎么改都不心疼,墙不能随便砸。虚拟 DOM 的价值,就是把昂贵的“砸墙”变成便宜的“改图”,这就是它的核心价值。
- 建图成本\(C_{create}\) 表示根据组件状态创建新虚拟 DOM 树的成本,本质是 new 一些 JS 对象,速度极快。
- 对比成本\(C_{diff}\) 表示新旧虚拟 DOM 树做差异比较的成本,稍后你会看到它被优化到线性级。
- 补丁成本\(C_{patch}\) 表示把差异应用到真实 DOM 上的成本,只操作有变化的那几个节点。
- 重建成本\(C_{rebuild}\) 表示把整棵真实 DOM 树卸载重建的成本,涉及布局计算与重绘,数量级远超前者。
虚拟DOM工作流程——画图、审图、施工四步法
R 队和 V 队虽然风格不同,但接单后的流程出奇一致。第一步是“量房”:组件收到新的状态(state)后,重新执行渲染函数,把当前界面完整地画成一份新图纸,这就是 render 阶段。第二步是“画图”:渲染函数返回的是一棵新的虚拟 DOM 树,树上每个节点都是轻量的 JS 对象。
第三步是“审图”:装修队长左手拿着旧图纸,右手拿着新图纸,逐层比对,找出所有变化的房间和墙面的位置,这个动作就是 diff。第四步是“施工”:队长拿着差异清单,派工人精确修改真实 DOM 中对应的位置,这个动作叫 patch。四步走完,页面就完成了更新。
注意一个关键点:diff 阶段只发生在 JS 对象的“图纸空间”里,完全没有触碰真实 DOM。直到 patch 阶段才把差异一次性提交给浏览器。这就像图纸评审会上可以随意讨论方案,确定方案后工人只跑一趟工地,避免了传统装修队“改一次拆一次房”的浪费。
这套流程最妙的地方在于:真实 DOM 的更新被集中在了最后一步,前面的所有环节都是在“纸面”上完成的低成本推演。R 队和 V 队的区别,只在于第三步“审图”用了完全不同的策略。
- 生成新图纸\(V_{new} = render(S)\) 表示组件状态 \(S\) 变化后,渲染函数产出一棵全新的虚拟 DOM 树 \(V_{new}\)。
- 求得差异\(\Delta = diff(V_{old},\, V_{new})\) 表示把旧树 \(V_{old}\) 和新树 \(V_{new}\) 对比,得到差异集合 \(\Delta\)。
- 施工更新\(patch(\Delta)\) 表示根据差异集合 \(\Delta\),在真实 DOM 上完成最小化的更新操作。
Diff核心思想——找差异的三条铁律
两家装修队都知道,最理想的审图方案是算出“把旧房子改成新房子”的最小改动方案——比如移动几堵墙、拆除几个房间。但这件事在计算机里属于“树编辑距离”问题,复杂度高达 \(O(n^3)\),节点一多,图纸还没审完,客户都等睡着了。所以两队都放弃了完美方案,改用工程化的近似策略。
第一条铁律:只比较同一层,不跨层移动。在真实项目里,把整个组件子树从一层挪到另一层的操作极其罕见,所以两家都默认“跨层移动 = 销毁重建”。有节点从第 2 层搬到第 3 层?两边直接把它卸了重新装,不做任何移动优化。第二条铁律:按广度逐层扫描,上一层比完再比下一层,绝不回头。第三条铁律:用 key 给节点配身份证。在同一层的兄弟节点列表里,key 帮算法快速判断“这个新节点以前是不是那一个旧节点”,没有 key 就只能靠位置蒙。
这三条铁律把原本指数级的比较难题,压缩成了线性的扫描任务,复杂度从 \(O(n^3)\) 直降到 \(O(n)\)。所谓 Diff 算法,本质就是“在工程可接受的误差内,用最小的代价找出新旧两棵树的差异”。就像装修队审图时不去推演墙体移动的最优路径,而是简单地逐层标记“哪间房变了,哪间房没变”。
这三条铁律是两支装修队的“行规”。但行规之下,R 队和 V 队的实操风格截然不同,我们分别去看看他们是怎么审图的。
- 指数灾难\(O(n^3)\) 是朴素树编辑距离的复杂度,节点数上百后就无法接受,必须用近似策略替代。
- 线性优化\(O(n)\) 是同层比较后的复杂度,每层节点只被访问常数次,整体与节点总数成正比。
- 节点规模\(n\) 表示虚拟 DOM 树的节点总数,是复杂度的基准单位。
React Diff策略——R队的标准排查法
R 队审图有一套严格的标准动作。队长站在门口,先看客厅(根节点)的“用途”变没变——在代码里这就是节点的 type。如果客厅变成了车库,类型都不同,那整栋房子直接推倒重盖,里面的所有房间都不必再对比。如果 type 相同,队长才继续检查客厅的家具摆放(props),然后进入下一层,逐一检查每个房间。
这就是 React 三大假设的第一条:不同类型的元素会产生不同的树,直接重建。第二条:开发者用 key 提示哪些子元素是稳定的,让列表对比时能复用节点。第三条:跨层移动极少发生,所以不深入处理。这三条假设像极了装修行业经验:用途不同的房间没法复用,有门牌号的房间才好认,没人会没事儿把整间房子搬到二楼去。
在层内列表对比时,React 的做法很朴素:把新旧两个列表按索引顺序位对齐,逐个比较。如果同一个位置上 key 相同,就认为这个节点可以复用,只更新变化的部分;如果 key 对不上,就标记为增删或重建。如果一个列表只有中间一个位置变了,React 也只会处理那一个位置,不过它不会像 Vue 那样聪明地从两头同时夹击。
R 队的方法稳定可靠、可预测性强,唯一的代价是:它不像 V 队那样有一张“key 快速定位表”,遇到列表中有节点被删掉再插回前面时,需要多花几步搬运。这正是 Vue 装修队琢磨出的改进点。
- 类型字段\(type\) 是节点的标签名或组件类型,React 遇到 type 变更,直接卸载整棵子树重建。
- 属性字段\(props\) 是节点的属性集合,type 相同才会继续对比 props 差异。
- 身份凭证\(key\) 是列表渲染时开发者提供的唯一标识,帮助 React 判断新旧节点是否为同一个。
- 子节点数组\(children\) 是递归进行下一层比较的子节点列表。
Vue Diff策略——V队的双端夹击
V 队审图的姿势比 R 队更灵活。他们不从门口一间间走到头,而是派两个人:一个从走廊最左端开始,一个从右端开始,同时向中间汇合。每轮比较,先看最左边的房间是否有变化,再看最右边的房间是否有变化,哪边相同就先处理哪边,两头同时推进。
这就是 Vue2 经典的双端比较(Double-End Diff)。每轮循环有四路判断:旧头对新头、旧尾对新尾、旧头对新尾、旧尾对新头,任一路命中就说明节点可以复用,剩下的交给 key 映射表快速查找。当列表里某个节点被删掉了,V 队能立刻发现尾部缺失,直接把它标记删除,而不必像 R 队那样把整段列表重新对位。
到了 Vue3,V 队又多了一把利器:编译时 patchFlag。因为 Vue 的模板是编译器处理的,它能在编译阶段就看出哪些地方是静态文本、哪些地方是动态属性,并在虚拟 DOM 节点上贴上“这里会变”的标记。运行时 diff 看到静态标记直接跳过,连比较都省了,只盯着真正会变的节点处理。
V 队的“双端夹击 + 门牌号快速查表 + 编译期贴标记”三件套,让它在处理列表增删和模板渲染时都比 R 队更省力。门牌号的价值在此时展露无遗:不用把所有房间都排查一遍,凭 key 就能直接定位要改的那间房。
- 旧头指针\(oldStart\) 指向旧列表最左端未处理节点的下标,每次命中头部匹配后自增。
- 旧尾指针\(oldEnd\) 指向旧列表最右端未处理节点的下标,命中尾部匹配后自减。
- 新头指针\(newStart\) 指向新列表最左端未处理节点的下标,头部命中后自增。
- 新尾指针\(newEnd\) 指向新列表最右端未处理节点的下标,尾部命中后自减。
两框架对比——R队与V队的正面交锋
现在让 R 队和 V 队同台竞技,看看两支装修队的真实差距。先说相同点:两队都用虚拟 DOM 做中间层,都遵守“同层比较 + key 复用”这两条铁律,最终都以 patch 的方式更新真实 DOM,这是行业共识,不是某家的独家秘笈。
真正的差异在第二层。R 队的 React 是纯运行时 diff:它不关心你的代码是怎么写的,每次更新都从根节点开始,把整棵虚拟 DOM 树扫一遍,靠三条假设砍掉不必要的比较。V 队的 Vue 则是运行时 + 编译时组合拳:编译阶段就通过 patchFlag 标记出了“哪些节点是死的”,运行时只扫活的节点,天然跳过静态内容。
因此在同等规模下,\(T_{Vue}\) 的实际比较节点数明显少于 \(T_{React}\),尤其适合模板大量、交互相对固定的中后台页面。而 React 的纯运行时方案换来了更高的灵活性——JSX 可以写在任何地方,diff 逻辑统一且可预测,大型复杂应用里生态更成熟、掌控感更强。没有绝对的谁好谁坏,只有匹配不匹配。
总结成一句:R 队靠“标准流程 + 经验假设”保证不犯错,V 队靠“提前做功课 + 精准定位”节省体力。两支队伍没有绝对的优劣,而是给人留下了两个不同的优化思路——这也是为什么理解 diff 差异,能帮你在真实项目里做出更聪明的渲染决策。
- React 的线性\(T_{React} \approx O(n)\) 表示 React 每次更新要把新旧整棵树完整扫描,比较次数与节点数成正比。
- Vue 的上界\(T_{Vue} \le O(n)\) 表示 Vue 因为跳跃了静态标记节点,比较次数总是小于等于节点总数。
- 常数优势实际差距体现在常系数上,Vue 扫的节点更少,常数更小,复杂模板下优势更明显。
性能优化建议——让装修队省力的诀窍
既然已经摸清了两支装修队的工作方式,那我们作为“客户”,怎么配合才能让他们更快干完活?第一个诀窍:把门牌号挂对。列表渲染时给每项一个稳定且唯一的 key,比如数据库里的 id。最忌讳的是用数组下标 index 当 key——列表一增删,下标全部错位,React 和 Vue 都会把原节点误判成新节点,导致一堆不该重建的节点被重建,甚至引发输入框内容错乱。
第二个诀窍:别随便改房子的承重结构。顶层组件的根标签要从 <div> 切成 <section>,或者把一个组件从“渲染 A 组件”改成“渲染 B 组件”?类型一变,React 会把整棵子树推倒重建,你精心优化的内部组件全部白搭。保持顶层结构稳定,diff 才有机会深入内层做精细比较。
第三个诀窍:让不需要装修的房间自动锁门。React 里用 React.memo 或 PureComponent 对组件做浅比较,让 props 没变的子组件跳过重新渲染;Vue 里配合 computed 自动缓存计算结果。这样父组件无论怎么折腾,子组件只要“没收到新指令”就直接说:我这间房不用改。下面这个复用率公式,就是衡量门牌号和结构做得好不好的标尺。
把这三个诀窍做到位,你写出来的组件哪怕面对每秒几十次的数据刷新,也能让页面保持流畅。理解虚拟 DOM 和 Diff,不是为了背概念,而是为了在写代码时知道:每一次渲染,装修队都在按什么规则为你省下真金白银。
- 复用节点数\(N_{matched}\) 是 diff 时判定为“同一个节点可直接复用”的数量,是优化成效的直接体现。
- 总节点数\(N_{total}\) 是新虚拟 DOM 树里需要处理的节点总数。
- 复用率趋近 1\(R \rightarrow 1\) 表示几乎所有节点都能被复用,只有真正的差异触发 patch,渲染性能达到最优。
AI 应用场景
大型表格渲染优化
阶段:前端组件性能优化
解决的问题:AI 系统的运营后台常常有上千行的实时数据表,订单、日志、指标每秒都在刷新。传统做法每次刷新都重新渲染整张表格,浏览器频繁重排,页面卡成幻灯片;用 index 做 key 还会导致行错位、输入框内容串行。
具体案例:某电商 AI 风控系统的订单流水表,通过 WebSocket 每秒推送新订单。前端给每行绑定订单号 orderId 作为稳定 key,并给行组件包裹 React.memo,让 diff 只命中真正变化的那几行。改造后表格从 300ms 刷新时长降到 30ms 以内。
可视化大屏实时刷新
阶段:数据渲染管线优化
解决的问题:AI 可视化大屏往往同时挂着十多个图表组件,数据流每秒多次更新。如果每次更新都让所有图表重新 diff 和渲染,CPU 会长期占用高企,动画掉帧。
具体案例:城市交通 AI 大屏的车辆轨迹图,每秒钟收到上千条轨迹点。开发团队将每个轨迹图层拆成独立组件,用稳定 key 标记每辆车,配合 patchFlag 精准更新变化图层,未变化的静态图层被直接跳过,大屏动画稳定在 60 帧。
框架选型评估
阶段:技术架构决策
解决的问题:AI 产品的 Web 端在选型时,需要评估渲染性能与开发效率的平衡。React 的纯运行时 diff 灵活可控,适合交互复杂、需要精细管理渲染的 AI 工作台;Vue 的编译时优化开箱即用,适合模板密集、快速迭代的中后台系统。
具体案例:某 AI 公司同时维护两个产品:模型标注工具选择了 React,因为画布交互复杂、团队需要手动控制重渲染边界;内部 AI 服务管理后台选择了 Vue,因为页面模板多、组件更新频率低,Vue 的静态优化让开发几乎不用手动做 memo。
Python 演示
def react_style_scan(old_list, new_list): """React 风格:按索引从前往后逐一比对""" ops = [] n = max(len(old_list), len(new_list)) for i in range(n): old = old_list[i] if i < len(old_list) else None new = new_list[i] if i < len(new_list) else None tag = "复用" if old == new else "更新" ops.append((old, new, tag)) return ops def vue_style_scan(old_list, new_list): """Vue 风格:双端指针同时向中间扫描""" ops = [] old = list(old_list) new = list(new_list) while old and new: if old[0] == new[0]: ops.append((old[0], new[0], "头部匹配")) old.pop(0) new.pop(0) elif old[-1] == new[-1]: ops.append((old[-1], new[-1], "尾部匹配")) old.pop() new.pop() elif old[0] == new[-1]: ops.append((old[0], new[-1], "头尾复用")) old.pop(0) new.pop() elif old[-1] == new[0]: ops.append((old[-1], new[0], "尾头复用")) old.pop() new.pop(0) else: ops.append((old[0], new[0], "未知需查表")) old.pop(0) new.pop(0) return ops def reuse_rate(ops): """统计可复用节点的比例""" reused = sum(1 for _, _, tag in ops if "复用" in tag or "匹配" in tag) return reused / max(len(ops), 1) if __name__ == "__main__": old_rooms = ["客厅", "卧室", "书房", "厨房"] new_rooms = ["客厅", "书房", "主卧", "厨房"] print("React 风格扫描:") for step in react_style_scan(old_rooms, new_rooms): print(" ", step) print("Vue 风格扫描:") for step in vue_style_scan(old_rooms, new_rooms): print(" ", step) print("Vue 风格复用率:", reuse_rate(vue_style_scan(old_rooms, new_rooms)))
def react_style_scan(old_list, new_list):模拟 React 在层内按索引顺序逐个比较子节点。n = max(len(old_list), len(new_list))按较长列表的长度对位,保证每个位置都被检查到。tag = "复用" if old == new else "更新"同位置同值判定为可复用,否则标记为需要更新。while old and new:Vue 双端比较的循环条件,只要有未处理的节点就继续扫描。elif old[-1] == new[-1]:尾部节点相同直接复用,这是双端比较省力的关键。elif old[0] == new[-1]:旧头部跑到新尾部也能识别,避免了 React 式的整段重排。elif old[-1] == new[0]:旧尾部跑到新头部同样可以捕获复用机会。ops.append((old[0], new[0], "未知需查表"))两头都无法匹配,交给 key 映射表做精确查找。if __name__ == "__main__":直接运行脚本即可看到两种策略的扫描轨迹差异。
常见误区
误区听说虚拟 DOM 快,那我就永远用它,直接操作 DOM 是愚蠢的行为。反正虚拟 DOM 性能天下第一。
正解虚拟 DOM 快在“频繁小改动”和“批量更新”的场景,它用 JS 对象计算换取了更少的真实 DOM 操作。但如果只改一个按钮的文案,直接 textContent 赋值可能比走完“创建 vnode → diff → patch”全流程更快。虚拟 DOM 的最大价值不是绝对速度,而是让开发者不用手动管理 DOM 操作,同时保证可接受的性能。
为何常见:框架宣传和面试题都强调虚拟 DOM 性能好,初学者容易把“相对优势”理解成“绝对优势”。
误区列表数据反正只有文字,用 index 当 key 也不会报错,还能少写一行代码,应该没问题吧?
正解列表一旦发生头部插入或删除,index 会全部错位。比如列表 [A,B,C] 删掉 A 后变成 [B,C],此时下标 0 的元素从 A 变成 B,而 key 却还是 0,框架会误以为“原来的 A 变成了 B”,导致本应复用的 B 的 DOM 被错误地当作 A 来更新,可能引发输入框内容串行、组件状态错乱。正确做法是用数据唯一的 id 做 key。
为何常见:初学者在静态列表上没踩过坑,看不到 index 在动态增删时的连锁反应,自然觉得“能用就行”。
误区React 和 Vue 都用虚拟 DOM,也都同层比较,那它们的 diff 就是同一个东西,只是打包方式不同。
正解两者虽然都遵守“同层比较 + key 复用”,但实现差异很大:React 是纯运行时,靠三大假设简化,列表层用单循环按索引遍历;Vue 在运行时之外还有编译期 patchFlag 标记,列表层用双端指针 + key 映射表。Vue3 还能跳过静态节点,React 则需要开发者手动 memo 来达到类似效果。
为何常见:只看到了“同层比较”的共同点,没有深入到列表比较策略和编译时优化这两个关键差异层面。
误区Diff 算法就是把新旧两棵树的每个节点都递归地互相比较一遍,找出所有不同之处,所以复杂度是 O(n²)。
正解实际上 React 和 Vue 都采用“同层比较”策略,只比较相同层级的节点,默认不跨层移动。之所以敢这样简化,是因为跨层移动在实际业务中极为罕见,强行支持反而会带来 O(n³) 的复杂度灾难。所以复杂度被压缩到 O(n),而不是递归全树两两比较。
为何常见:看到“树”就联想到全递归遍历,忽略了框架工程上“用假设换性能”的取舍智慧。
面试题
Q1. 虚拟 DOM 的本质是什么?
- A.浏览器里的内存对象
- B.用JS对象模拟真实DOM← 正确答案
- C.一套全新的 HTML 标签
- D.浏览器自带的渲染引擎
答案:B。虚拟 DOM 是一棵由 JavaScript 普通对象构成的树,用对象结构描述真实 DOM 的样子,而不是浏览器原生对象。它就像装修队在动工前画的设计图纸,本质是轻量的 JS 数据,不是浏览器内存里的真实节点。
Q2. 虚拟 DOM 最核心的优势是什么?
- A.减少真实DOM操作次数← 正确答案
- B.彻底消灭页面重绘流程
- C.让 HTML 代码更加简洁
- D.让 CSS 样式自动生效
答案:A。虚拟 DOM 把多次零散的 DOM 操作合并成一次 patch,集中批量更新,从而减少浏览器重排重绘。它不可能消灭重绘(页面总得画出内容),也不负责 HTML 简洁或 CSS 生效,那些不是它的职责。
Q3. 下列哪一项是 React Diff 的三大假设之一?
- A.不同类型的元素产生不同树← 正确答案
- B.开发者必须给每个节点加 key
- C.所有组件都必须是纯函数
- D.Diff 只能在整个应用顶层进行
答案:A。React 的第一个假设是“不同类型的元素会产生不同的树”,所以 type 变了直接重建。key 是列表优化建议而非强制;纯函数是组件规范不是 diff 假设;diff 是递归在每一层进行的,不是只在顶层。
Q4. 在列表中不推荐使用 index 作为 key 的原因?
- A.index 不会随列表发生变化
- B.index 会随增删导致错位复用← 正确答案
- C.index 是数字而不是字符串
- D.index 无法通过编译器校验
答案:B。列表增删时 index 会重新编号,框架误以为旧节点变成了新节点,导致 key 与节点对应关系错乱。这就像门牌号按排队序号来贴,队伍一插人,全部房间身份都乱了,复用逻辑必然出错。
Q5. Vue 的 Diff 最突出的特点是什么?
- A.只比较同层不做跨层移动
- B.双端指针同时向中间扫描← 正确答案
- C.每次更新都会重建整棵树
- D.完全依赖编译时的静态代码
答案:B。Vue2 列表 diff 采用双端比较,头尾四路同时推进;Vue3 又加上了编译时 patchFlag。A 是两家共同点而非 Vue 独有,C 和 D 表述错误。
Q6. React 与 Vue 的 Diff 都遵循的规则是?
- A.跨层移动节点不进行深入比较← 正确答案
- B.每个节点必须标记为静态节点
- C.使用双端指针快速匹配复节点
- D.只在初次渲染时执行 diff
答案:A。两家都默认“跨层移动极少发生”,所以跨层移动的节点被直接卸载重建,不做精细比较。静态标记是 Vue3 特有,双端指针是 Vue 特有,diff 在每次更新时都会执行,D 明显错误。
Q7. 虚拟 DOM 的优点有哪些?
- A.减少频繁的真实DOM操作← 正确答案
- B.让框架可以批量更新视图← 正确答案
- C.让浏览器不需要解析 HTML
- D.使渲染性能一定超过原生
答案:AB。虚拟 DOM 通过批量 patch 减少真实 DOM 操作,这是核心优势。浏览器仍然要解析 HTML,C 错;性能上并非必然超过原生操作,D 是绝对化说法,错误。
Q8. 属于 React 与 Vue Diff 差异的有?
- A.Vue 对静态节点有编译标记← 正确答案
- B.React 靠三大假设简化 diff← 正确答案
- C.Vue 使用双端指针推进扫描← 正确答案
- D.React 会复用跨层移动的节点
答案:ABC。Vue3 编译时标记静态节点,React 依赖三大假设,Vue 列表使用双端比较,这些都是真实差异。React 明确假设跨层移动罕见,遇到跨层移动直接重建,D 错误。
Q9. 虚拟 DOM 的工作流程顺序是什么?
- A.vnode创建→diff对比→patch更新
- B.render触发→vnode创建→diff→patch← 正确答案
- C.diff对比→vnode创建→patch→render
- D.patch更新→diff→render→vnode创建
答案:B。状态变化先触发 render 函数,产出新的虚拟 DOM 树(vnode),然后与旧树 diff 得到差异,最后 patch 更新真实 DOM。顺序就像“量房→画图→审图→施工”,一步都不能乱。
Q10. 以下哪些是提升渲染性能的正确做法?
- A.列表key保持稳定且唯一← 正确答案
- B.尽量保持组件顶层结构稳定← 正确答案
- C.对昂贵组件使用 memo 缓存← 正确答案
- D.每次渲染前手动删除多余节点
答案:ABC。稳定 key 提高复用率,顶层结构稳定避免整棵子树重建,memo 拦截不必要的子组件重渲,三者都是标准优化手段。手动删除节点违背框架声明式思想,还会干扰 diff 的正确性,是反模式。