围绕虚拟DOM原理与Diff算法对比展开深入探讨,从基本概念出发逐步深入到高级应用。内容涵盖前端开发与AI交互、Web前端基础中的虚拟DOM原理与Diff算法对比原理、关键算法、性能特性及优化策略,结合实际案例分析常见问题与解决方案,为工程实践提供系统性指导,适合希望深入理解该领域的技术人员学习参考。

学习提示

需要学习虚拟DOM原理与Diff算法对比,涉及DOM、Diff、AI等核心概念。从基本概念出发逐步深入到高级应用。作为Web前端基础 > 37.1.2 前端框架React/Vue/Angular/Svelte的核心组成,掌握虚拟DOM原理与Diff算法对比有助于打通相关知识链条。

目录
  1. 可视化演示
  2. AI 应用场景
  3. Python 演示
  4. 常见误区
  5. 面试题

可视化演示

状态—

围绕虚拟DOM原理与Diff算法对比展开深入探讨,从基本概念出发逐步深入到高级应用。内容涵盖前端开发与AI交互、Web前端基础中的虚拟DOM原理与Diff算法对比原理、关键算法、性能特性及优化策略,结合实际案例分析常见问...

虚拟DOM概述

报纸排好了,读者点了一个按钮,编辑只是想把标题里的一个字改掉。如果按老规矩,印刷厂得把整张报纸重新排版、重新印刷——浏览器里的真实 DOM 操作正是这样:哪怕只改一行文字,页面也要经历重排和重绘,成本高得吓人。虚拟 DOM 的思路是:先在内存里用 JavaScript 对象画一份“电子草稿”,改完草稿后,再让“校稿员”对比新旧两份草稿,找出真正变化的几个字,印刷厂只重印这几个字对应的补丁块。

在技术层面,虚拟 DOM 的本质是一个描述 UI 的 JavaScript 对象树。它诞生的背景,就是当时 jQuery 时代频繁手写 DOM 操作导致页面性能失控、代码难维护。虚拟 DOM 要解决的痛点不是“绝对最快”,而是“在频繁变更时,用可控的成本完成界面更新”。它的标准工作流程分为三步:先根据数据构建新的虚拟 DOM 树,再通过 Diff 算法与旧树对比,最后把差异批量应用到真实 DOM 上。

我们可以用成本模型来直观理解它为什么值得:

左边的式子表示虚拟 DOM 方案的总开销,右边的式子表示传统方案遍历全量节点的代价,实际场景中右边往往大得多。

Tvd=C建图+C比对+C打补丁≪T直改=n×C重排T_{\mathrm{vd}} = C_{\mathrm{建图}} + C_{\mathrm{比对}} + C_{\mathrm{打补丁}} \ll T_{\mathrm{直改}} = n \times C_{\mathrm{重排}}
  1. 总成本符号T_{\mathrm{vd}} 表示虚拟 DOM 方案的总耗时,它由三部分相加构成。 Tvd=C建图+C比对+C打补丁T_{\mathrm{vd}} = C_{\mathrm{建图}} + C_{\mathrm{比对}} + C_{\mathrm{打补丁}}
  2. 三个成本项C_{\mathrm{建图}} 是构建新草稿的成本,C_{\mathrm{比对}} 是校稿员对比差异的成本,C_{\mathrm{打补丁}} 是把差异应用到页面的成本。 C建图+C比对+C打补丁C_{\mathrm{建图}} + C_{\mathrm{比对}} + C_{\mathrm{打补丁}}
  3. 传统方案成本n 是页面节点总数,C_{\mathrm{重排}} 是每个节点参与重排的平均代价,传统方案几乎要付 n 倍的它。 n×C重排n \times C_{\mathrm{重排}} 草稿纸虽然多画了一份,但避免了整张报纸每次都重印。当页面越大、变化越少时,这套“先草稿、再校稿、后补丁”的方案就越划算——这就是虚拟 DOM 能被两大框架同时选中的根本原因。

虚拟DOM构建

草稿纸不是白纸一张,它有固定的格子:每个格子记录“这是什么版块”“有什么样式”“里面装了哪些子栏目”。虚拟 DOM 的节点对象也一样,它用结构化的方式来描述页面上的一小块区域,这个对象在 React 中被称为 VNode(虚拟节点)。

一个 VNode 通常由三部分组成:标签名、属性对象、子节点数组。标签名告诉框架“这里是一张图片还是一个段落”,属性对象记录 class、style、事件等附加信息,子节点数组则指向下一层级的虚拟节点。这就像报纸的格子既知道自己是什么栏目,也知道自己下面嵌套了哪些小栏目。

这个三元组是虚拟 DOM 的最小单元,所有复杂页面都是这种单元一层层嵌套而成的。

VNode=(tag, props, children)\mathrm{VNode} = (tag,\ props,\ children)
  1. 结构整体\mathrm{VNode} 表示一个虚拟节点,等号右边说明它由三个字段打包而成。 VNode=(tag, props, children)\mathrm{VNode} = (tag,\ props,\ children)
  2. 标签字段tag 对应真实 DOM 的标签,比如 div、span,决定草稿格子的形状。 tag∈{div, span, p, ⋯ }tag \in \{ \text{div},\ \text{span},\ \text{p},\ \cdots \}
  3. 属性与子节点props 保存属性和样式,children 保存下一级虚拟节点,形成树状嵌套。 props={class, style, onClick, ⋯ },children=[ VNode, ⋯ ]props = \{ \text{class},\ \text{style},\ \text{onClick},\ \cdots \}, \quad children = [\ \mathrm{VNode},\ \cdots \ ] 虚拟 DOM 构建完成后,框架会把这个草稿格子“印刷”成真实 DOM:先创建真实元素,再设置属性,最后递归挂载子节点。整个过程就像把草稿纸上的格子一个个变成报纸上的栏目,每当你修改数据,框架就重新构建一份新草稿,然后交给校稿员。

Diff核心原理

校稿员拿到两份草稿,如果逐字逐句地做全排列比对,那效率低得离谱。最朴素的树形 Diff 算法需要对每个节点去匹配其他所有节点,复杂度高达 O(n³),页面稍大就会卡成幻灯片。聪明的工程师们定下了三条“校稿铁律”:只在同层节点之间比较、不同标签类型的节点直接重建、开发者用 key 标记节点的身份。

这三条铁律把问题大大简化。同层比较意味着校稿员不再跨楼层东奔西跑,只看同一高度的内容;标签不同直接重建,省去了无谓的匹配尝试;而 key 就像快递单号,能让校稿员一眼认出“这个格子之前在哪个位置”。有了 key,Diff 的复杂度从 O(n³) 降到了 O(n),线性地扫描一遍就能完成比对。

复杂度从立方级降到线性级,是虚拟 DOM 能被大规模应用的关键原因。

Tdiff(n)=O(n),O(n3)→O(n)T_{\mathrm{diff}}(n) = O(n), \qquad O(n^3) \to O(n)
  1. 优化前代价朴素树 Diff 需要把两棵树的节点两两匹配,最坏情况下复杂度是 O(n^3)。 O(n3)O(n^3)
  2. 优化后代价通过同层比较和 key 定位,每个节点在每层只需被扫描常数次,总耗时变为线性。 Tdiff(n)=O(n)T_{\mathrm{diff}}(n) = O(n)
  3. 箭头含义这个箭头表示“从立方级降低到线性级”,是三大策略叠加的收益。 O(n3)→O(n)O(n^3) \to O(n) 校稿员的效率之所以能飙升,是因为他不做无意义的全排列尝试,而是靠“同层 + 标签 + key”三个约束精准锁定差异。如果把页面比作一栋大楼,他每层只检查同一高度上的房间,房间类型变了就整间重建,门牌号对上了就直接复用——这就是 Diff 算法的灵魂。

React的Diff

React 的校稿员有个习惯:拿起尺子,一头从草稿的第一块开始比,另一头从最后一块开始比,两边同时往中间走。这就是 React Diff 的“双指针”策略,它特别适合处理列表头部或尾部发生变化的情况。头尾能匹配上的块直接复用,匹配不上的再进入中间的详细比对。

在整个 Diff 过程中,React 还会维护一个 lastIndex,它记录“当前已处理节点里,旧列表索引最大的那一位”。当某个节点的旧索引小于 lastIndex 时,说明它相对靠前的位置已经被其他节点占用,这个节点就需要被移动到新位置。这个优化让 React 不需要每次移动都遍历全表,而是用一个滚动标记就把“是否要移动”判断出来。

上面的式子表明:如果某个节点在旧列表中的索引,小于当前已遍历节点索引的最大值,该节点就需要移动。

Ilast=max⁡{Ip0, Ip1, ⋯ , Ipk−1},Ip<Ilast⇒移动I_{\mathrm{last}} = \max\{ I_{p_0},\ I_{p_1},\ \cdots,\ I_{p_{k-1}} \}, \qquad I_{p} < I_{\mathrm{last}} \Rightarrow \text{移动}
  1. lastIndex 含义I_{\mathrm{last}} 是已处理节点中旧索引的最大值,像一根“最右标杆”。 Ilast=max⁡{Ip0, Ip1, ⋯ , Ipk−1}I_{\mathrm{last}} = \max\{ I_{p_0},\ I_{p_1},\ \cdots,\ I_{p_{k-1}} \}
  2. 移动条件I_p 是当前节点的旧索引,当它小于标杆时,意味着这个块要靠前,需要移动。 Ip<IlastI_{p} < I_{\mathrm{last}}
  3. 触发动作箭头右边表示触发移动操作,React 会把该节点插入到新位置。 ⇒移动\Rightarrow \text{移动} 不过这位“左右夹击的校稿员”也有局限:它只能逐层处理,对跨层级的移动并不友好——一个节点从第 2 层挪到第 3 层,React 宁可把它当作“旧块删除、新块新增”。这就是为什么 React 文档一直强调:保持层级稳定,配合 key 使用,校稿员才能发挥最大威力。

Vue的Diff

Vue 的校稿员更会“偷懒”——他在草稿排版阶段就拿到了秘密武器:编译器会给每个内容块贴好标签。静态标签表示“这块内容永远不变”,动态标签表示“这块内容可能随数据变化”。校稿员拿到新草稿后,先扫一眼标签,静态块直接跳过,只对动态块做精细比对,这就是 Vue 的静态提升与 patchFlag 机制。

patchFlag 可以看作一个“变化类型码”,它以位运算的方式记录节点具体哪里变了:是文本变了、class 变了,还是绑定了事件。这比 React 老老实实进入节点内部逐字段检查更精准。同时,Vue 在列表 Diff 中也采用双端比较:从新旧列表的头部和尾部同时开始,通过头头对比、尾尾对比、头尾交叉对比等策略,能更高效地识别“把最后一块挪到最前边”这类位移操作。

这个式子说明:Vue 判定一个旧节点能否复用需要同时满足 key 相等、标签相等、并且可以被 patch 更新。

P复用=(key等)∧(tag等)∧(可 patch)P_{\mathrm{复用}} = (key_{\mathrm{等}}) \wedge (tag_{\mathrm{等}}) \wedge (\text{可 patch})
  1. 复用判定变量P_{\mathrm{复用}} 表示“节点可复用”这个结论,它等于多个条件同时成立。 P复用=A∧B∧CP_{\mathrm{复用}} = A \wedge B \wedge C
  2. 条件一与二key_{\mathrm{等}} 表示新旧节点的 key 一致,tag_{\mathrm{等}} 表示标签类型一致。 key等∧tag等key_{\mathrm{等}} \wedge tag_{\mathrm{等}}
  3. 条件三\text{可 patch} 表示当前节点允许被增量更新,而不是必须重建。 ∧(可 patch)\wedge (\text{可 patch}) Vue 的校稿员像一位看惯了版式的老编辑,静态板块扫一眼就翻过去,动态板块才停下来细看。因为 Vue 能在编译阶段拿到更多信息,它往往能在静态内容较多的页面上做到比 React 更少的 Diff 工作量。

双框架对比

从草稿纸到补丁,React 和 Vue 走的是两条不同路线的流水线。React 属于“运行时泛用派”:它的 diff 逻辑完全内置在运行时,你给它什么样的组件树,它就用双端夹击的方式尽量复用节点。Vue 属于“编译优化派”:它在模板编译期做了静态分析,标记好哪些节点永远不变,运行时就能跳过这些无用功。

从性能特征上看,Vue 在有大量静态模板的业务页面中优势明显,因为 patchFlag 和静态提升砍掉了大量无意义的 Diff;React 的动态渲染能力更强,尤其是在完全由数据驱动的复杂交互界面中,它的双端 Diff 配合 Fiber 架构(这里的 Fiber 不在本文范围)也能保持稳定表现。若抽象成公式,React 的耗时几乎随节点总数线性增长,而 Vue 的耗时大致只与动态节点数量相关:

其中 p 是静态节点占比,k 和 k' 是常数。静态占比越高,Vue 的 Diff 耗时越低。

TReact≈k⋅n,TVue≈k′⋅(1−p)⋅nT_{\mathrm{React}} \approx k \cdot n, \qquad T_{\mathrm{Vue}} \approx k' \cdot (1-p) \cdot n
  1. React 耗时模型T_{\mathrm{React}} 约等于常数 k 乘以节点总数 n,每个节点都逃不过扫描。 TReact≈k⋅nT_{\mathrm{React}} \approx k \cdot n
  2. Vue 耗时模型(1-p) 表示动态节点占比,静态占比 p 越高,参与 Diff 的节点越少。 TVue≈k′⋅(1−p)⋅nT_{\mathrm{Vue}} \approx k' \cdot (1-p) \cdot n
  3. 对比结论当 p 接近 1,也就是页面几乎全静态时,Vue 的 Diff 开销趋近于零。 lim⁡p→1 TVue→0\lim_{p \to 1}\ T_{\mathrm{Vue}} \to 0 选型建议也很直白:如果你的业务是大量 CRUD 表单、文档展示这类“模板化”页面,Vue 的编译优化能让校稿员省下大把力气;如果你的业务是图表大屏、AI 对话流这类高度动态、交互频繁的界面,React 的运行时泛用性会给你更多灵活性。两条流水线没有绝对的优劣,只有适配度的高低。

工程优化实践

再好的校稿员,也需要编辑配合。工程实践中最常见的问题,是开发者给了校稿员一堆“糊涂编号”——比如用数组 index 当 key。当列表在头部插入一项时,所有元素的 index 都会变,校稿员以为每个格子都换了位置,于是把整列都翻了个底朝天。正确做法是用稳定且唯一的业务 ID 作为 key,让校稿员能认出“这张老面孔还是原来那个位置”。

第二招是组件拆分。把大组件拆成小组件,相当于把一大张草稿拆成多张可单独校对的活页,某一块变化时,校稿员只需要取那张活页反复核对,其他活页原封不动。第三招是减少不必要的更新触发:数据没变就不重新渲染,避免把校稿员白白叫来一趟。可以用命中率和浪费率来量化这些优化的收益:

h 表示旧节点被成功复用的比例,W 表示白白浪费的 Diff 比例。优化目标就是让 h 趋向 1。

h=mn,W=1−hh = \frac{m}{n}, \qquad W = 1 - h
  1. 命中率m 是被成功复用的旧节点数,n 是总节点数,命中率 h 衡量复用效率。 h=mnh = \frac{m}{n}
  2. 浪费率W 是未命中率,表示校稿员白跑了一趟的节点占比。 W=1−hW = 1 - h
  3. 优化方向让 h \to 1、W \to 0,意味着绝大多数节点都轻松复用,只有少数节点真在更新。 h→1h \to 1 给校稿员减轻负担的三板斧——稳定 key、组件拆分、避免多余更新——最终都在做同一件事:让更多草稿块能“原样复用”。校稿员只有在真正需要的地方才动笔,印刷厂才只打最有价值的补丁,整个前端项目的性能自然就稳住了。

AI 应用场景

长列表虚拟滚动优化

阶段:前端性能优化

解决的问题:AI 对话流、日志查看器动辄渲染几千条消息,每条消息都是一个虚拟节点,传统全量 Diff 会一次性比对所有节点,导致输入延迟和滚动卡顿。虚拟滚动通过只渲染可视区域内的节点,把 Diff 范围从几千个缩小到几十个。

具体案例:某大模型对话产品在流式输出回答时,消息列表每秒追加多个 token,采用“可视窗口 + 稳定消息 ID 作为 key”的方案后,首屏渲染时间从 800ms 降到 120ms,连续输出 200 条消息不再掉帧。

相关角色:前端工程师、AI 应用开发者

中后台表单动态增删

阶段:数据驱动页面更新

解决的问题:智能客服后台的工单表单需要根据用户选择动态增删字段,每次增删都触发整个表单重新渲染。Vue 的 patchFlag 机制能提前标记出哪个字段区域是动态的,Diff 只作用在变化的小块上。

具体案例:某 AI 标注平台的高精度表单,点击“增加标注项”时,原本整个表单的补偿重绘被替换为局部 patch,页面响应时间由 200ms 缩短到 30ms,标注员连续操作不再感觉页面卡顿。

相关角色:前端工程师、低代码平台开发工程师

大屏数据面板的更新裁剪

阶段:可视化渲染层优化

解决的问题:AI 运维大屏每秒钟推送上千条指标数据,如果整屏组件都参与 Diff,CPU 占用率飙升。通过组件拆分把每个图表封装成独立虚拟 DOM 子树,配合稳定的数据主键,只有数值变化的那张图表才被校稿。

具体案例:某智慧园区大屏同时展示 30 个实时图表,优化后更新周期从 2 秒降为 500ms,CPU 占用率从 70% 降到 25%,大屏滚动展示不再有白屏闪烁。

相关角色:前端工程师、数据可视化工程师

Python 演示

python71 行
"""简化版虚拟DOM:用字典模拟 VNode,实现同层 key 比对""" def create_element(tag, key, props=None, children=None):    return {'tag': tag, 'key': key, 'props': props or {}, 'children': children or []}  def render(vnode, indent=0):    print(' ' * indent + '<%s key=%s>' % (vnode['tag'], vnode['key']))    for child in vnode['children']:        render(child, indent + 2)    print(' ' * indent + '</%s>' % vnode['tag'])  def diff_children(old_list, new_list):    ops = []    old_keys = {node['key'] for node in old_list}    new_keys = {node['key'] for node in new_list}    for node in old_list:        if node['key'] not in new_keys:            ops.append(('delete', node['key']))    for i, node in enumerate(new_list):        if node['key'] not in old_keys:            ops.append(('add', node['key'], i))    for node in new_list:        if node['key'] in old_keys:            ops.append(('update', node['key']))    return ops  def patch(root_old, root_new):    ops = []    if root_old['tag'] != root_new['tag']:        return [('replace', root_new['key'])]    if root_old['key'] != root_new['key']:        return [('replace', root_new['key'])]    if root_old['props'] != root_new['props']:        ops.append(('update_props', root_new['key']))    ops.extend(diff_children(root_old['children'], root_new['children']))    return ops  def main():    old_root = create_element(        'ul', 'list',        children=[            create_element('li', 'item-1', children=[create_element('span', 'text-1')]),            create_element('li', 'item-2', children=[create_element('span', 'text-2')]),            create_element('li', 'item-3', children=[create_element('span', 'text-3')])        ]    )    new_root = create_element(        'ul', 'list',        children=[            create_element('li', 'item-1', children=[create_element('span', 'text-1')]),            create_element('li', 'item-2', children=[create_element('span', 'text-2-new')]),            create_element('li', 'item-4', children=[create_element('span', 'text-4')])        ]    )    print('旧草稿:')    render(old_root)    print('新草稿:')    render(new_root)    print('Diff结果:')    for op in patch(old_root, new_root):        print(' ', op)  if name == 'main':    main()

常见误区

误区列表数据只要渲染顺序正确,key 写不写无所谓,用 index 当 key 也一样能跑。

正解key 是校稿员的“快递单号”,index 是数组里的座位号。在头部插入一项后,所有座位的编号都向后挪了一位,校稿员拿着旧单号对新座位,发现每个节点都“变了位置”,于是把整列节点全部移动甚至重建,性能反而更差。更严重的是,某些带输入框的列表会因此串数据——输入框一挪,内容全乱。

为何常见:初学者通常只看到“页面能正常显示”,看不到渲染背后 Diff 的匹配路径,只有在数据量变大或状态串位时才会意识到问题。

误区用 React/Vue 就是比原生 DOM 快,因为虚拟 DOM 天然有性能优势。

正解虚拟 DOM 是“草稿纸 + 校稿 + 补丁”,这套流程本身就是额外的成本。如果页面只有一个按钮的文本变化,直接 textContent 改一个字可能只需几微秒,而虚拟 DOM 要先建整棵树、做 Diff、再打补丁,反而更慢。虚拟 DOM 的真正价值是工程一致性:在复杂、频繁更新的场景下,把不可控的重排重绘收敛为可控的批量 patch。

为何常见:框架官方文档强调虚拟 DOM 带来了可预测的性能,人们容易把“可预测”误解为“绝对更快”。

误区Diff 算法会像深拷贝一样,把新树和旧树的每个叶子节点都递归遍历一遍,所以节点越多越慢。

正解Diff 只在“同层”之间比较,也就是新旧树同一高度的节点互相匹配,不做跨层移动找节点。如果某个节点跨了层,直接判定为旧节点删除、新节点新建。这个“同层近似”原则让复杂度从 O(n³) 降到了 O(n),代价是放弃了一部分可复用的跨层移动。

为何常见:很多人从“树形结构”的字面意思出发,以为 Diff 会做深度遍历,却没注意到框架用工程近似换性能的核心思路。

误区Vue 有编译期优化,所以凡是 Vue 项目都比 React 项目快。

正解Vue 的 patchFlag 和静态提升在模板静态内容多时确实能减少 Diff 工作量,但 React 的运行时 Diff 在高度动态的数据驱动场景下同样高效,且 React 的生态组件复用能力更强。决定性能的不只 Diff 本身,还有虚拟滚动、组件拆分、请求策略等工程手段。拿“某一场合更省力”当“处处更优”,属于以偏概全。

为何常见:技术文章的标题容易放大差异,读者记住了结论,却忽略了对比的前提条件。

面试题

Q1. 虚拟 DOM 的本质上是什么?

单选题 · easy

  • A.一段独立的 JavaScript 文件
  • B.描述 UI 结构的 JavaScript 对象← 正确答案
  • C.浏览器提供的渲染接口
  • D.一种新的 HTML 语法

答案:B。虚拟 DOM 是 JavaScript 对象树,用来描述真实 UI 结构。A 选项是 JS 文件,C 是浏览器 API,D 是 HTML 语法,都不符合定义。它相当于“草稿纸上的报纸版面”。

Q2. 直接操作真实 DOM 的主要性能开销来自哪里?

单选题 · easy

  • A.浏览器重排与重绘开销较大← 正确答案
  • B.JavaScript 代码不易调试
  • C.工程师写代码速度过慢
  • D.服务器同步负担过重

答案:A。真实 DOM 变更会触发浏览器的重排和重绘,这是最昂贵的部分。B、C 属于开发体验问题,D 与前端渲染无关。所以虚拟 DOM 的思路是把多次离散的重排收敛为一次 patch。

Q3. React Diff 算法优化后的复杂度大致是多少?

单选题 · easy

  • A.常数级 O(1)
  • B.线性级 O(n)← 正确答案
  • C.平方级 O(n²)
  • D.立方级 O(n³)

答案:B。React 通过同层比较、类型判断和 key 定位,把朴素树对比的 O(n³) 降到 O(n)。O(1) 不现实,O(n²) 和 O(n³) 都还是优化前的代价量级。

Q4. Vue 的 patchFlag 主要作用是什么?

单选题 · mid

  • A.标记节点是否为静态节点
  • B.记录节点具体动态类型← 正确答案
  • C.替代 key 完成节点复用
  • D.存储节点全部子节点信息

答案:B。patchFlag 用位标记记录节点“哪里变了”,比如文本、class、事件等。A 是静态标记的一部分,但不全面;C 和 D 都是干扰项,patchFlag 不替代 key,也不存储子节点。

Q5. 为什么列表 key 不宜使用数组 index?

单选题 · mid

  • A.index 在插入后不可读取
  • B.顺序变化导致节点错位复用← 正确答案
  • C.index 必须是字符串类型
  • D.index 会减慢编译速度
  • E.直接删除该节点
  • F.将该节点移动到新位置
  • G.立即中断整个比对
  • H.原索引保持不变

答案:B。lastIndex 是已遍历节点中最大的旧索引,当当前节点的旧索引更小时,说明它需要往左/往前移动。React 会把它移动到新位置。删除中断都不对,索引也不可能保持不变。

Q6. Vue 双端比较最擅长处理哪类场景?

单选题 · mid

  • A.大量静态节点构成的页面
  • B.列表头尾频繁插入删除← 正确答案
  • C.深层组件嵌套较多的页面
  • D.表单控件集中的复杂页面

答案:B。双端比较从新旧列表的头尾同时比对,能快速识别“头插”“尾插”“头尾互换”等位移操作。A 是静态提升的擅长场景,C、D 与双端比较的直接关系较弱。

Q7. 以下哪些属于 React Diff 的三大设计策略?

多选题 · hard

  • A.跨层级移动时直接重建节点← 正确答案
  • B.相同类型节点才进行复用← 正确答案
  • C.用 key 标记同层节点身份← 正确答案
  • D.跨层结构会尽量移动复用

答案:ABC。三大策略是:不同标签类型直接重建、用 key 指出节点身份、同层比较且跨层视为重建。A、B、C 分别对应这三点。D 说跨层尽量移动复用,与策略相反,跨层在 React 中往往重建。

Q8. 一次完整的虚拟 DOM 更新包含哪些步骤?

多选题 · mid

  • A.构建新的虚拟节点树← 正确答案
  • B.Diff 找出新旧差异← 正确答案
  • C.将补丁应用到真实 DOM← 正确答案
  • D.每次都必须序列化 HTML

答案:ABC。标准三步是构建新树、Diff 对比、patch 打补丁。D 的序列化 HTML 是服务端渲染或某些特殊手段的产物,不是虚拟 DOM 每次都做的事。

Q9. 下列哪些手段能有效提升 Diff 的复用率?

多选题 · hard

  • A.使用稳定且唯一的 key← 正确答案
  • B.按功能拆分到细分组件← 正确答案
  • C.避免多余的全量更新触发← 正确答案
  • D.使用随机数字作为 key 加速

答案:ABC。稳定唯一 key 让校稿员认得出原节点,组件拆分缩小 diff 范围,避免多余更新减少无效比对。D 用随机数当 key 会导致每次都匹配不上,复用率反而降为零。