框架原理深度解析

#前端#框架#原理

前端面试题 - 框架原理深度解析(React vs Vue)

本章节深度解析 React 和 Vue 的核心原理,包括虚拟 DOM、Diff 算法、响应式系统、Fiber 架构、Hooks 原理等。适合高级前端工程师面试准备。


📊 数据统计

统计项数值
总题数60道
基础题15道 (25%)
中级题28道 (46.7%)
高级题17道 (28.3%)

📚 目录


一、虚拟 DOM 原理

1. 什么是虚拟 DOM?为什么要使用它?

难度:⭐⭐(中级)
标签:#虚拟DOM

参考答案要点

定义:虚拟 DOM 是用 JavaScript 对象描述真实 DOM 结构的轻量级表示。

JAVASCRIPT
// 真实 DOM
;<div id="app" class="container">
  <h1>Hello</h1>
  <p>World</p>
</div>

// 虚拟 DOM
const vdom = {
  tag: "div",
  props: { id: "app", class: "container" },
  children: [
    { tag: "h1", props: {}, children: ["Hello"] },
    { tag: "p", props: {}, children: ["World"] },
  ],
}

使用原因

优势说明
性能优化减少直接操作 DOM,批量更新
跨平台虚拟 DOM 可映射到不同平台(Web/Native/小程序)
开发体验声明式编程,无需手动操作 DOM
一致性保证 UI 与状态同步

为什么不直接操作 DOM?

  • DOM 操作是昂贵的(重排重绘)
  • 虚拟 DOM 可以批量处理更新
  • 通过 Diff 算法最小化 DOM 操作

2. 虚拟 DOM 真的比直接操作 DOM 快吗?

难度:⭐⭐⭐(高级)
标签:#虚拟DOM #性能

参考答案要点

不是绝对的,虚拟 DOM 的优势在于批量更新和跨平台,而非单纯的速度。

场景对比

场景直接操作 DOM虚拟 DOM
单次更新慢(需要生成 VDOM + Diff)
批量更新慢(多次重排)快(合并更新)
复杂应用难以优化自动优化

虚拟 DOM 的价值

  1. 开发效率:声明式编程,专注状态管理
  2. 自动优化:Diff 算法找出最小更新
  3. 跨平台:同一套代码运行在不同环境

结论:虚拟 DOM 是为了开发体验和跨平台,性能是副产品。


3. Vue 和 React 的虚拟 DOM 有什么区别?

难度:⭐⭐⭐(高级)
标签:#虚拟DOM #React #Vue

参考答案要点

特性ReactVue
创建方式JSX + createElementTemplate 编译
编译优化较少(依赖开发者)较多(编译时优化)
更新粒度组件级别组件级别
静态提升有(Vue 3)
PatchFlag有(Vue 3)

Vue 3 编译优化

VUE
<template>
  <div>
    <h1>静态标题</h1>
    <!-- 静态提升,跳过 Diff -->
    <p>{{ dynamic }}</p>
    <!-- 标记为动态,只比较这里 -->
  </div>
</template>

编译后会标记动态节点,Diff 时只比较动态部分。


二、Diff 算法对比

4. React 的 Diff 算法原理是什么?

难度:⭐⭐⭐(高级)
标签:#Diff #React

参考答案要点

核心策略

  1. 树 Diff

    • 只比较同一层级的节点
    • 跨层级移动节点,直接销毁重建
  2. 组件 Diff

    • 同一类型组件:继续 Diff 子树
    • 不同类型组件:销毁旧组件,创建新组件
  3. 元素 Diff

    • 通过 key 识别节点
    • 三种操作:插入、移动、删除

Diff 流程

JAVASCRIPT
// 简化版 Diff
function diff(oldNode, newNode) {
  // 1. 类型不同,直接替换
  if (oldNode.type !== newNode.type) {
    return { type: "REPLACE", newNode }
  }

  // 2. 文本节点
  if (typeof oldNode === "string") {
    if (oldNode !== newNode) {
      return { type: "TEXT", content: newNode }
    }
    return null
  }

  // 3. 比较属性
  const propsPatches = diffProps(oldNode.props, newNode.props)

  // 4. 比较子节点
  const childrenPatches = diffChildren(oldNode.children, newNode.children)

  return { type: "UPDATE", props: propsPatches, children: childrenPatches }
}

5. Vue 的 Diff 算法原理是什么?

难度:⭐⭐⭐(高级)
标签:#Diff #Vue

参考答案要点

核心策略

  1. 同层比较:只比较同一层级的节点
  2. 双端比较:新旧列表从两端向中间比较
  3. key 优化:通过 key 复用节点

双端比较算法

Plain Text
旧列表:[a, b, c, d]
新列表:[b, a, d, c]

步骤:
1. 旧头 a vs 新头 b → 不同
2. 旧头 a vs 新尾 c → 不同
3. 旧尾 d vs 新头 b → 不同
4. 旧尾 d vs 新尾 c → 不同
5. 从旧列表找 b → 找到,移动到头部
6. 继续比较...

Vue 2 vs Vue 3

特性Vue 2Vue 3
算法双端比较双端比较 + 最长递增子序列
静态节点无优化静态提升
PatchFlag有,标记动态内容

6. React 和 Vue 的 Diff 算法有什么区别?

难度:⭐⭐⭐(高级)
标签:#Diff #React #Vue

参考答案要点

特性ReactVue
比较方向从左到右双端比较
列表优化需要 key需要 key
移动检测较弱较强(双端)
时间复杂度O(n)O(n)

React 的不足

  • 只能从左到右比较
  • 列表末尾移动到开头,会全部重建

Vue 的优势

  • 双端比较,能更快找到可复用节点
  • 使用最长递增子序列优化移动操作

示例对比

JAVASCRIPT
// 旧列表:[a, b, c]
// 新列表:[c, a, b]

// React:全部重建(无法识别移动)
// Vue:识别移动,复用节点

7. 为什么列表需要 key?index 作为 key 有什么问题?

难度:⭐⭐(中级)
标签:#Diff #key

参考答案要点

key 的作用

  1. 唯一标识:帮助框架识别每个节点的身份
  2. 复用节点:通过 key 找到对应节点,避免销毁重建
  3. 保持状态:确保组件状态与正确的 DOM 关联

index 作为 key 的问题

JAVASCRIPT
// 列表:[{id: 1, name: 'A'}, {id: 2, name: 'B'}]
// 渲染:<li key={index}>{item.name}</li>

// 在头部插入新项
// 新列表:[{id: 3, name: 'C'}, {id: 1, name: 'A'}, {id: 2, name: 'B'}]

// 问题:
// key 0: A → C(复用错误,状态混乱)
// key 1: B → A(复用错误)
// key 2: 无 → B(新建)

// 结果:全部更新,性能差,状态混乱

正确做法:使用唯一 ID 作为 key

JAVASCRIPT
<li key={item.id}>{item.name}</li>

三、响应式系统

8. Vue 2 的响应式原理是什么?

难度:⭐⭐⭐(高级)
标签:#响应式 #Vue2

参考答案要点

核心机制:数据劫持 + 发布订阅模式

JAVASCRIPT
// 简化版实现
function defineReactive(obj, key, val) {
  const dep = new Dep() // 依赖收集器

  Object.defineProperty(obj, key, {
    get() {
      // 收集依赖
      if (Dep.target) {
        dep.depend()
      }
      return val
    },
    set(newVal) {
      if (newVal !== val) {
        val = newVal
        // 通知更新
        dep.notify()
      }
    },
  })
}

class Dep {
  constructor() {
    this.subs = []
  }

  depend() {
    if (Dep.target) {
      this.subs.push(Dep.target)
    }
  }

  notify() {
    this.subs.forEach((watcher) => watcher.update())
  }
}

局限性

  • 无法检测对象属性的新增/删除
  • 无法检测数组索引的变化
  • 需要遍历对象所有属性,性能开销

9. Vue 3 的响应式原理是什么?

难度:⭐⭐⭐(高级)
标签:#响应式 #Vue3

参考答案要点

核心机制:Proxy + Reflect

JAVASCRIPT
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      track(target, key) // 依赖收集
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      const oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)
      if (oldValue !== value) {
        trigger(target, key) // 触发更新
      }
      return result
    },
    deleteProperty(target, key) {
      const hadKey = Object.prototype.hasOwnProperty.call(target, key)
      const result = Reflect.deleteProperty(target, key)
      if (hadKey && result) {
        trigger(target, key)
      }
      return result
    },
  })
}

优势对比

特性Vue 2 (defineProperty)Vue 3 (Proxy)
新增属性无法监听自动监听
删除属性无法监听自动监听
数组索引无法监听自动监听
Map/Set不支持支持
性能初始化遍历所有属性懒代理,按需触发

10. React 的 setState 是同步还是异步的?

难度:⭐⭐⭐(高级)
标签:#setState #React

参考答案要点

既可能是同步,也可能是异步,取决于执行上下文。

异步情况(默认)

JAVASCRIPT
// React 事件处理函数中
handleClick = () => {
  this.setState({ count: this.state.count + 1 })
  console.log(this.state.count) // 旧值(异步)
}

同步情况

JAVASCRIPT
// setTimeout 中
setTimeout(() => {
  this.setState({ count: this.state.count + 1 })
  console.log(this.state.count) // 新值(同步)
}, 0)

// 原生事件监听中
document.addEventListener("click", () => {
  this.setState({ count: this.state.count + 1 })
  console.log(this.state.count) // 新值(同步)
})

原因

  • React 事件处理函数中:批量更新(Batch Update)机制
  • setTimeout/原生事件:脱离 React 控制,同步执行

React 18 的改进

  • 自动批处理(Automatic Batching)
  • 所有情况下都是异步的

11. React 的批量更新(Batch Update)机制是什么?

难度:⭐⭐⭐(高级)
标签:#批量更新 #React

参考答案要点

定义:将多个 setState 合并为一次更新,减少渲染次数。

JAVASCRIPT
// 批量更新前(3 次渲染)
handleClick = () => {
  this.setState({ a: 1 })
  this.setState({ b: 2 })
  this.setState({ c: 3 })
}

// 批量更新后(1 次渲染)
handleClick = () => {
  this.setState({ a: 1 })
  this.setState({ b: 2 })
  this.setState({ c: 3 })
  // 合并为 { a: 1, b: 2, c: 3 },只渲染一次
}

实现原理

JAVASCRIPT
// 简化版实现
let isBatchingUpdates = false
let pendingState = []

function setState(newState) {
  if (isBatchingUpdates) {
    // 批量更新中,暂存状态
    pendingState.push(newState)
  } else {
    // 直接更新
    updateComponent(newState)
  }
}

function batchedUpdates(fn) {
  isBatchingUpdates = true
  fn()
  isBatchingUpdates = false

  // 合并所有状态,执行一次更新
  const finalState = Object.assign({}, ...pendingState)
  pendingState = []
  updateComponent(finalState)
}

React 18 自动批处理

JAVASCRIPT
// React 18 中,所有情况都自动批处理
setTimeout(() => {
  setCount((c) => c + 1)
  setFlag((f) => !f)
  // 只渲染一次
}, 1000)

四、React Fiber 架构

12. 什么是 React Fiber?为什么要引入?

难度:⭐⭐⭐(高级)
标签:#Fiber #React

参考答案要点

Fiber 定义

  • 新的协调引擎(Reconciliation)
  • 重新设计的组件树遍历算法
  • 支持可中断和恢复的渲染

为什么需要 Fiber

React 15 的问题

  • 递归遍历组件树,无法中断
  • 大量组件更新时,阻塞主线程
  • 页面卡顿,用户体验差

Fiber 的解决方案

  • 将渲染工作拆分为小单元
  • 每个单元执行后检查是否有高优先级任务
  • 有则暂停渲染,让出主线程
Plain Text
React 15: 递归渲染(阻塞)
├─ 渲染 A
│  ├─ 渲染 B
│  │  ├─ 渲染 C(耗时 200ms,页面卡顿)
│  │  └─ 渲染 D
│  └─ 渲染 E
└─ 渲染 F

React 16+: Fiber(可中断)
├─ 渲染 A → 检查 → 继续
├─ 渲染 B → 检查 → 继续
├─ 渲染 C → 检查 → 有高优先级任务 → 暂停
├─ 执行高优先级任务
└─ 恢复渲染 C → 完成

13. Fiber 的工作原理是什么?

难度:⭐⭐⭐⭐(专家)
标签:#Fiber #React

参考答案要点

Fiber 结构

JAVASCRIPT
interface Fiber {
  type: any,           // 组件类型
  key: null | string,  // key
  stateNode: any,      // 真实 DOM 或组件实例

  // 链表指针
  return: Fiber | null,    // 父节点
  child: Fiber | null,     // 第一个子节点
  sibling: Fiber | null,   // 下一个兄弟节点

  // 工作单元
  pendingProps: any,   // 新 props
  memoizedProps: any,  // 旧 props
  memoizedState: any,  // 旧 state

  // 副作用
  effectTag: number,   // 副作用类型(增删改)
  nextEffect: Fiber | null, // 下一个副作用节点
}

双缓冲技术

Plain Text
current Fiber Tree(当前显示)
    ↓
workInProgress Fiber Tree(正在构建)
    ↓
渲染完成,交换指针
    ↓
workInProgress 变为 current

工作流程

  1. Render 阶段(可中断):

    • 构建 Fiber 树
    • 计算变化(Diff)
    • 标记副作用
  2. Commit 阶段(不可中断):

    • 执行副作用(DOM 操作)
    • 执行生命周期
    • 执行回调

14. Fiber 如何实现时间切片?

难度:⭐⭐⭐⭐(专家)
标签:#Fiber #时间切片

参考答案要点

时间切片:将长任务拆分为多个小任务,每执行完一个检查是否超时。

实现原理

JAVASCRIPT
// 简化版实现
const FRAME_DURATION = 5 // 每帧最多 5ms 用于 React 渲染

function workLoop(deadline) {
  let shouldYield = false

  while (nextUnitOfWork && !shouldYield) {
    // 执行一个 Fiber 单元
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)

    // 检查是否超时
    shouldYield = deadline.timeRemaining() < FRAME_DURATION
  }

  if (nextUnitOfWork) {
    // 还有工作,请求下一帧继续
    requestIdleCallback(workLoop)
  } else {
    // 完成,提交更改
    commitRoot()
  }
}

// 启动
requestIdleCallback(workLoop)

优先级调度

JAVASCRIPT
// 不同优先级的更新
const priorities = {
  ImmediatePriority: 1, // 立即执行(用户输入)
  UserBlockingPriority: 2, // 用户阻塞(点击)
  NormalPriority: 3, // 正常优先级
  LowPriority: 4, // 低优先级
  IdlePriority: 5, // 空闲时执行
}

五、Hooks 原理

15. React Hooks 的原理是什么?

难度:⭐⭐⭐⭐(专家)
标签:#Hooks #React

参考答案要点

Hooks 本质:利用闭包和数组来保存状态。

实现原理(简化版)

JAVASCRIPT
// React 内部实现
const React = (function () {
  let hooks = [] // 存储所有 state
  let idx = 0 // 当前 hook 的索引

  function useState(initialValue) {
    const state = hooks[idx] !== undefined ? hooks[idx] : initialValue
    hooks[idx] = state

    const _idx = idx
    idx++

    function setState(newValue) {
      hooks[_idx] = newValue
      render() // 重新渲染
    }

    return [state, setState]
  }

  function render(Component) {
    idx = 0 // 每次渲染重置索引
    const comp = Component()
    comp.render()
    return comp
  }

  return { useState, render }
})()

// 使用
function MyComponent() {
  const [count, setCount] = React.useState(0)
  const [name, setName] = React.useState("React")

  return {
    render: () => {
      console.log({ count, name })
    },
    click: () => setCount(count + 1),
  }
}

为什么不能在 if 中使用 Hooks

  • Hooks 依赖调用顺序
  • if 中调用会导致索引错乱
  • 状态与组件对应关系混乱

16. useEffect 和 useLayoutEffect 的区别?

难度:⭐⭐⭐(高级)
标签:#Hooks #Effect

参考答案要点

特性useEffectuseLayoutEffect
执行时机渲染完成后异步执行渲染完成后同步执行(浏览器绘制前)
阻塞渲染是(可能导致卡顿)
使用场景大多数副作用需要同步执行的副作用(测量 DOM)

执行流程对比

Plain Text
useEffect:
组件渲染 → 绘制到屏幕 → 执行 useEffect

useLayoutEffect:
组件渲染 → 执行 useLayoutEffect → 绘制到屏幕

使用建议

  • 优先使用 useEffect
  • 只有出现视觉闪烁时,才使用 useLayoutEffect

17. useMemo 和 useCallback 的区别?

难度:⭐⭐(中级)
标签:#Hooks #性能优化

参考答案要点

特性useMemouseCallback
缓存内容计算结果函数本身
返回值计算结果函数
使用场景昂贵的计算子组件回调、依赖数组

关系

JAVASCRIPT
useCallback(fn, deps) === useMemo(() => fn, deps)

使用示例

JAVASCRIPT
// useMemo:缓存计算结果
const expensiveValue = useMemo(() => {
  return computeExpensiveValue(a, b)
}, [a, b])

// useCallback:缓存函数
const handleClick = useCallback(() => {
  doSomething(a, b)
}, [a, b])

六、组件更新机制

18. React 组件何时会重新渲染?

难度:⭐⭐(中级)
标签:#渲染 #React

参考答案要点

触发重新渲染的情况

  1. state 变化

    JAVASCRIPT
    setState({ count: 1 }) // 重新渲染
  2. props 变化

    JAVASCRIPT
    // 父组件传递新的 props
    <Child name="new" />
  3. 父组件重新渲染

    JAVASCRIPT
    // 即使 props 不变,父组件渲染子组件也会渲染
  4. forceUpdate

    JAVASCRIPT
    this.forceUpdate() // 强制重新渲染

优化手段

  • React.memo(函数组件)
  • shouldComponentUpdate/PureComponent(类组件)
  • useMemo/useCallback

19. Vue 组件何时会重新渲染?

难度:⭐⭐(中级)
标签:#渲染 #Vue

参考答案要点

触发重新渲染的情况

  1. 响应式数据变化

    JAVASCRIPT
    this.count++ // 触发重新渲染
  2. props 变化

    JAVASCRIPT
    // 父组件传递新的 props
  3. 父组件重新渲染

    JAVASCRIPT
    // 子组件会重新渲染

优化手段

  • computed(缓存计算结果)
  • watch(精确控制更新)
  • v-once(只渲染一次)
  • shouldComponentUpdate(React 概念,Vue 无直接对应)

20. React.memo 和 Vue 的 computed 有什么区别?

难度:⭐⭐⭐(高级)
标签:#性能优化 #React #Vue

参考答案要点

特性React.memoVue computed
作用缓存组件渲染结果缓存计算结果
比较方式浅比较 props依赖追踪
使用场景组件级优化数据计算优化
自定义比较支持自定义函数不支持

React.memo 示例

JAVASCRIPT
const MyComponent = React.memo(
  function MyComponent({ name }) {
    return <div>{name}</div>
  },
  (prevProps, nextProps) => {
    // 自定义比较
    return prevProps.name === nextProps.name
  }
)

Vue computed 示例

JAVASCRIPT
const fullName = computed(() => {
  return firstName.value + " " + lastName.value
})

七、性能优化原理

21. React 的 Time Slicing(时间切片)是什么?

难度:⭐⭐⭐(高级)
标签:#性能优化 #React

参考答案要点

定义:将渲染工作拆分为多个小任务,分散到多个帧中执行。

与 Fiber 的关系

  • Fiber 是架构基础
  • Time Slicing 是 Fiber 的能力之一

实现原理

JAVASCRIPT
// 使用 requestIdleCallback 或 MessageChannel
function workLoop(hasTimeRemaining) {
  while (nextUnitOfWork && hasTimeRemaining()) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
  }

  if (nextUnitOfWork) {
    // 时间用完,下一帧继续
    scheduleCallback(workLoop)
  }
}

用户感知

  • 大量数据渲染时,页面不会卡顿
  • 用户输入响应更及时

22. Vue 3 的静态提升(Static Hoisting)是什么?

难度:⭐⭐⭐(高级)
标签:#性能优化 #Vue3

参考答案要点

定义:编译阶段将静态节点提取出来,避免每次渲染重新创建。

示例

VUE
<template>
  <div>
    <h1>静态标题</h1>
    <!-- 静态节点 -->
    <p>{{ dynamic }}</p>
    <!-- 动态节点 -->
  </div>
</template>

编译结果

JAVASCRIPT
const _hoisted_1 = /*#__PURE__*/ createElementVNode(
  "h1",
  null,
  "静态标题",
  -1 /* HOISTED */
)

export function render(_ctx, _cache) {
  return (
    openBlock(),
    createElementBlock("div", null, [
      _hoisted_1, // 直接复用静态节点
      createElementVNode(
        "p",
        null,
        toDisplayString(_ctx.dynamic),
        1 /* TEXT */
      ),
    ])
  )
}

优势

  • 减少内存分配
  • 减少 Diff 工作量
  • 提升渲染性能

23. React 的 Concurrent Mode 是什么?

难度:⭐⭐⭐⭐(专家)
标签:#ConcurrentMode #React

参考答案要点

定义:React 18 引入的新特性,使渲染可中断,提升用户体验。

核心特性

  1. 可中断渲染

    • 渲染过程中可以暂停
    • 优先处理高优先级任务(用户输入)
  2. 自动批处理

    • 所有更新自动批处理
    • 减少渲染次数
  3. Suspense

    • 更好的异步处理
    • 数据获取与渲染分离

使用方式

JAVASCRIPT
// React 18 默认启用
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<App />)

// useTransition:标记非紧急更新
const [isPending, startTransition] = useTransition()

startTransition(() => {
  setSearchQuery(input) // 非紧急更新,可中断
})

复习建议

重点题目清单

必会题目(⭐⭐⭐ 高频)

  1. 虚拟 DOM 原理和优势
  2. React 和 Vue 的 Diff 算法区别
  3. key 的作用和注意事项
  4. Vue 2 和 Vue 3 响应式原理
  5. setState 同步/异步问题
  6. Fiber 架构和优势
  7. Hooks 原理和使用规则

重点掌握(⭐⭐ 常考)

  1. 批量更新机制
  2. useEffect vs useLayoutEffect
  3. useMemo vs useCallback
  4. 组件更新机制
  5. 性能优化手段

加分项(⭐ 了解)

  1. 时间切片实现
  2. Concurrent Mode
  3. 静态提升原理

学习建议

  1. 阅读源码

    • Vue 3 响应式系统(reactivity 包)
    • React Fiber 实现(ReactFiber 文件)
  2. 画图理解

    • Diff 过程
    • Fiber 链表结构
    • 响应式依赖收集
  3. 手写实现

    • 简化版 Vue 响应式
    • 简化版 React Hooks
    • 虚拟 DOM + Diff

最后更新于: 2026-02-27