文章目录
- [一、React 面试题:深度解析虚拟 DOM (Virtual DOM)](#一、React 面试题:深度解析虚拟 DOM (Virtual DOM))
-
- [1. 什么是虚拟 DOM (Virtual DOM)?](#1. 什么是虚拟 DOM (Virtual DOM)?)
- [2. 虚拟 DOM 的工作原理](#2. 虚拟 DOM 的工作原理)
- [3. 性能优化机制](#3. 性能优化机制)
-
- [A. 批量更新 (Batching)](#A. 批量更新 (Batching))
- [B. 差异化更新 (Diffing Algorithm)](#B. 差异化更新 (Diffing Algorithm))
- [C. Key 属性的作用](#C. Key 属性的作用)
- [D. 内存开销权衡](#D. 内存开销权衡)
- [总结:虚拟 DOM 的本质意义](#总结:虚拟 DOM 的本质意义)
- [React 面试题:深入理解 JSX](#React 面试题:深入理解 JSX)
-
-
- [1. JSX 的语法规则](#1. JSX 的语法规则)
- [2. JSX 的本质是什么?](#2. JSX 的本质是什么?)
- [3. 为什么浏览器无法直接解析 JSX?](#3. 为什么浏览器无法直接解析 JSX?)
-
- [React 面试题:如何理解 U I = f ( s t a t e ) UI = f(state) UI=f(state)?](#React 面试题:如何理解 U I = f ( s t a t e ) UI = f(state) UI=f(state)?)
-
-
- [1. 公式含义拆解](#1. 公式含义拆解)
- [2. 为什么这是革命性的?](#2. 为什么这是革命性的?)
- [3. f f f 到底做了什么?(React 的工作机制)](#3. f f f 到底做了什么?(React 的工作机制))
- [4. 这一思想的延伸](#4. 这一思想的延伸)
- [5. 面试总结话术](#5. 面试总结话术)
-
一、React 面试题:深度解析虚拟 DOM (Virtual DOM)
虚拟 DOM 不仅仅是一个技术概念,更是 React 性能和开发体验的核心所在。我们可以从定义 、工作原理 、性能优化三个维度来系统性地回答。
1. 什么是虚拟 DOM (Virtual DOM)?
虚拟 DOM 是一个轻量级的 JavaScript 对象,它是对真实 DOM 的一种数学抽象。
在 React 中,你编写的 JSX 并不是直接渲染到屏幕上的真实 HTML,而是先被编译成一个个普通的 JS 对象。
| 特性 | 真实 DOM | 虚拟 DOM |
|---|---|---|
| 本质 | 浏览器的对象模型 | 内存中的 JavaScript 对象 |
| 开销 | 包含成百上千属性,操作昂贵 | 仅含核心属性(type, props, children),操作极快 |
| 影响 | 频繁操作会导致重绘 (Repaint) 和回流 (Reflow) | 在内存中计算差异,不会直接触发渲染 |
2. 虚拟 DOM 的工作原理
React 的更新流程通常被称为 Reconciliation(协调),主要分为以下三步:
- 生成虚拟树: 当应用的状态 (State) 或属性 (Props) 发生变化时,React 会重新调用
render方法,生成一棵全新的虚拟 DOM 树。 - Diff 算法(对比): React 将这棵"新树"与上一次渲染留下的"旧树"进行深度对比,找出变化的部分。
- Patch(打补丁): 找出两棵树之间真正的差异后,React 会计算出最小的操作集合,并将其一次性应用到真实 DOM 上。
3. 性能优化机制
虚拟 DOM 的快,本质上是通过以下策略减少了昂贵的 DOM 操作次数:
A. 批量更新 (Batching)
React 不会因为你连续调用 10 次 setState 就去操作 10 次真实 DOM。它会将多次状态变更收集起来,在一次事件循环中只执行一次虚拟 DOM 的 Diff 和真实的 DOM 渲染。
B. 差异化更新 (Diffing Algorithm)
React 的 Diff 算法具有 O ( n ) O(n) O(n) 的时间复杂度(而非传统的 O ( n 3 ) O(n^3) O(n3)),主要基于两个假设:
- 同层对比: 算法只会比较同一层级的节点,不会跨层级比较。
- 类型唯一: 如果两个元素的类型不同(比如从
<div>变成了<p>),React 会直接卸载旧节点并挂载新节点。
C. Key 属性的作用
在处理列表渲染时,key 是最重要的优化手段。它作为节点的唯一标识,帮助 React 在 Diff 过程中准确识别哪些元素是被移动、新增或删除的。
优化点: 避免了不必要的重新渲染。如果不使用
key,React 可能会采取"就地复用"策略,导致渲染错误或性能下降。
D. 内存开销权衡
虚拟 DOM 实际上是用内存空间 (存储 JS 对象)换取了计算时间。它通过在内存中完成复杂的计算,确保最终对浏览器 DOM 的干扰降到最低。
总结:虚拟 DOM 的本质意义
- 跨平台性: 因为虚拟 DOM 是抽象的 JS 对象,它可以被渲染到浏览器 (
ReactDOM)、移动端 (React Native) 甚至是服务器端 (SSR)。 - 性能下限保障: 它确保了即使开发者没有手动进行极致的 DOM 优化,应用也能拥有非常不错的性能表现。
- 声明式编程: 开发者只需关注数据状态 (
State),无需手动编写冗长且易错的 DOM 操作逻辑,极大提升了开发效率。
React 面试题:深入理解 JSX
在 React 的世界里,JSX(JavaScript XML)是开发者最亲密的伙伴。它看起来像 HTML,其实就是 JavaScript 的 语法拓展。以下是针对这三个面试核心问题的详细拆解:
1. JSX 的语法规则
为了保证代码的可读性和解析的准确性,JSX 遵循一套严格的规则:
- 单一根元素 :组件必须返回一个且仅一个根元素。如果你不想增加多余的 DOM 节点,可以使用空标签
<>...</>(即React.Fragment)。 - 标签必须闭合 :即便是像
<img>或<input>这种在 HTML 中可以不闭合的标签,在 JSX 中也必须写成<img />。 - 驼峰命名法 :由于 JSX 本质是 JS,属性名需遵循 JS 命名规范。
- 例如:
class变为className,onclick变为onClick,for变为htmlFor。
- 例如:
- JS 表达式嵌入 :在 JSX 中使用大括号
{}可以包裹任何有效的 JavaScript 表达式(变量、函数调用、三元运算等)。 - 属性值引号 :字符串属性用双引号
"",对象或变量属性用{}。
2. JSX 的本质是什么?
JSX 的本质是 React.createElement 函数调用的语法糖。
虽然我们在编辑器里写的是类似 HTML 的标签,但经过编译(通常由 Babel 完成)后,它会转化成普通的 JavaScript 对象。
演变过程:
-
代码层(JSX) :
javascript<h1 className="title">Hello</h1> -
编译层(Babel 转换后) :
javascriptReact.createElement('h1', { className: 'title' }, 'Hello'); -
运行层(VDOM) :该函数执行后返回一个 React Element(一个普通的 JS 对象),用来描述页面上应该出现什么。
3. 为什么浏览器无法直接解析 JSX?
原因很简单:JSX 不是标准的 ECMAScript 语法。
- 语法冲突 :浏览器只认识标准的 HTML、CSS 和 JavaScript。JSX 中的尖括号
<在 JS 上下文中会被解析器认为是非法的语法错误(除非它是小于号)。 - 非原生对象:浏览器引擎(如 Chrome 的 V8)没有内置处理这种"HTML 混合 JS"体的逻辑。
总结流程
由于浏览器看不懂 JSX,我们需要一个转译器(Transpiler)(如 Babel 或 SWC):
- 开发者编写 JSX 代码。
- 构建工具 (Webpack/Vite)调用 Babel。
- Babel 将 JSX 转换成标准的
React.createElement(或新的转换函数) JS 代码。 - 浏览器接收并运行转换后的纯 JavaScript。
💡 面试小贴士:
记得提到 React 17 之后引入的新转换(New JSX Transform) 。
现在编译器(Babel)会自动引入特殊的转换函数(如
_jsx),这使得你不再需要在每个文件顶部手动import React from 'react'了,但这并不改变 JSX 最终会被转化为函数调用和 JS 对象的本质。
React 面试题:如何理解 U I = f ( s t a t e ) UI = f(state) UI=f(state)?
在 React 的面试中,U I = f ( s t a t e ) UI = f(state) UI=f(state) 是一个极具深度的底层哲学问题。它不仅是一个公式,更是**声明式编程(Declarative Programming)**的核心思想。
1. 公式含义拆解
这个公式定义了 UI 与数据之间的一种映射关系:
- U I UI UI:用户看到的界面(在 React 中表现为 DOM 树或组件树)。
- f f f :组件函数(Component)。它包含了组件的逻辑、渲染过程以及对数据的处理。
- s t a t e state state :状态与数据 。这不仅包括组件内部的
state,也包括外部传入的props。
核心逻辑 :
UI 不再由开发者手动通过 DOM 操作来更改,而是作为数据的"副作用"产物。你只需要描述状态是什么样的,框架( f f f)就会自动计算出 UI 应该是什么样的。
2. 为什么这是革命性的?
在传统的命令式编程(如 jQuery)中,逻辑是:
"找到那个按钮,给它添加一个类名,然后修改它的文字。"
在 U I = f ( s t a t e ) UI = f(state) UI=f(state) 的模式下,逻辑转变为:
"当前按钮的状态是'已点击',请根据这个状态渲染界面。"
- 可预测性 :只要输入( s t a t e state state)是确定的,输出( U I UI UI)就一定是确定的。这大大降低了调试难度。
- 解耦:开发者只需要关注数据的变化,而不需要关注如何操作 DOM 去同步这些变化。
- 一致性:有效地避免了"状态已经改变,但 UI 没更新"这种常见的同步 Bug。
3. f f f 到底做了什么?(React 的工作机制)
当 s t a t e state state 发生变化时,函数 f f f 会重新执行,但 React 并不是简单地粗暴替换整个 DOM,而是经历了一个复杂的转换过程:
- 触发渲染 : s t a t e state state 改变,通知 React 需要更新。
- 生成 Virtual DOM:函数重新执行,生成一棵新的虚拟 DOM 树。
- Diff 算法:React 将新旧两棵虚拟 DOM 树进行对比,找出最小差异点。
- 提交更新(Commit):仅将差异部分应用到真实的浏览器 DOM 上。
4. 这一思想的延伸
这个公式还可以进一步演进为 U I = f ( s t a t e , c o n t e x t ) UI = f(state, context) UI=f(state,context) 或引入 Side Effects(副作用)。
在现代 React 开发中:
- Hooks :让函数组件 f f f 能够更方便地挂载和管理 s t a t e state state。
- 纯函数:理想情况下,组件应该尽可能接近"纯函数",即相同的输入永远得到相同的输出,不产生意料之外的影响。
5. 面试总结话术
"U I = f ( s t a t e ) UI = f(state) UI=f(state) 体现了 React 的声明式设计哲学。它意味着 UI 是状态的投影,我们通过管理状态来间接管理 UI。
这种模式将复杂的 DOM 维护工作交给了框架,开发者只需维护数据的正确性,从而提高了代码的可维护性和测试的可行性。React 的虚拟 DOM 和 Diff 算法则是为了让这个公式在高性能要求下依然成立的技术支撑。"