gpt面试题

发布于:2025-07-23 ⋅ 阅读:(18) ⋅ 点赞:(0)

vue面试题
💡 一、响应式系统相关
❓1. Vue 3 的响应式系统是如何实现的?和 Vue 2 有何本质区别?
答案:
Vue 3 使用 Proxy 实现响应式(位于 @vue/reactivity 模块),替代 Vue 2 的 Object.defineProperty。
核心机制如下:

使用 targetMap: WeakMap 存储依赖关系

利用 track() 和 trigger() 方法实现依赖收集与派发更新

effect() 包装副作用函数,自动收集依赖

响应式对象通过 reactive() 创建,底层用 Proxy 拦截 get/set

本质区别:

Vue 2:只能监听已有属性,新增/删除属性需 Vue.set

Vue 3:完全代理对象,支持深层嵌套、数组、Map/Set

❓2. Vue 3 是如何避免“无限递归依赖收集”的?
答案:
Vue 3 使用了 activeEffect 和 effectStack 来管理当前正在执行的副作用函数。
避免无限依赖循环的关键是:

if (shouldTrack && activeEffect) {
// 收集依赖
}
同时,在 effect() 中入栈前判断是否已经存在于栈内。

💡 二、虚拟 DOM 与 Diff 算法
❓3. Vue 3 的 patch 函数做了哪些优化?VNode 是如何分类处理的?
答案:
Vue 3 的 patch() 是递归处理新旧 vnode 的核心函数,优化包括:

利用 shapeFlag 标识节点类型,如:元素、组件、文本、Fragment 等

针对静态节点进行 Static Hoisting,跳过 patch

Fragment 和 Text 类型的专门处理

children diff 使用 keyed diff 和 最长递增子序列 算法,提升最小移动次数

VNode 创建时通过 createVNode() 自动计算 shapeFlag,以便 patch 阶段快速判断类型分支,提高性能。

💡 三、组件系统与生命周期
❓4. setup() 中返回的内容是如何绑定到组件实例上的?
答案:
setup() 执行完后返回的对象会被绑定到组件代理实例上,使用 withProxy 包装,在 render 中通过作用域代理访问。

代码核心位置:

const setupResult = setup(…)
// 将 setup 返回值代理到 ctx 上
instance.setupState = proxyRefs(setupResult)
最终通过 componentPublicInstance 的 proxy 实现 this.xxx 访问 setup() 中的变量。

❓5. defineComponent 的作用是什么?为什么 Vue 3 要使用它?
答案:
defineComponent 是一个类型辅助函数,本质上不影响运行时。其主要作用是配合 TS 推导组件类型,提升开发体验。

Vue 3 中组件是基于对象或函数定义,defineComponent 会对组件定义做 TS 友好包装,生成明确的类型声明,使 IDE 和 TS 能正确推导 props、emits、slots 等。

💡 四、架构设计
❓6. Vue 3 为何将核心功能模块拆分为多个包?(如 reactivity、runtime-core)
答案:
Vue 3 的架构采用“模块化 + 运行时编译解耦”设计,目的包括:

按需打包:Tree shaking 支持更好

包职责清晰:如 @vue/reactivity 只管响应式逻辑

支持定制渲染器:如 Vue Native / Vue for MiniApp 可以复用 core,只替换 runtime

主要模块:

reactivity:响应式系统

runtime-core:运行时核心逻辑

runtime-dom:浏览器环境渲染器

compiler-core:模板编译核心

compiler-dom:专门为浏览器优化编译器

💡 五、Composition API 与依赖收集
❓7. ref() 和 reactive() 有什么区别?底层实现上有何不同?
答案:

ref() 创建一个包含 .value 的响应式对象,适合基本类型

reactive() 返回 Proxy 对象,适合对象类型

实现上:

ref() 底层是 RefImpl 类,使用 Object.defineProperty 对 .value 实现响应式

reactive() 使用 createReactiveObject() 返回 Proxy,内部使用统一的 mutableHandlers

function ref(value) {
return new RefImpl(value)
}
❓8. computed 的懒执行机制是怎么实现的?
答案:
computed() 内部使用了 effect 并设置 lazy: true,不会立即执行计算,而是等 .value 被访问时执行。

通过 dirty 标志判断是否需要重新计算:

if (this._dirty) {
this._value = this.effect()
this._dirty = false
}
当依赖变化时,通过 scheduler 将 dirty 标志重新置为 true,延迟到下一次 .value 访问时再计算。

💡 六、模板编译与优化
❓9. Vue 模板是如何转换为渲染函数的?
答案:
模板编译包含三步:

解析阶段(Parse):将模板字符串转为 AST(抽象语法树)

转换阶段(Transform):AST 上做优化,如 hoist 静态节点、v-if/v-for 编译

生成阶段(Codegen):将 AST 转换为 JS 渲染函数代码

最终由 compiler-core 输出字符串形式的 render 函数,交由 Function 构造执行。

💡 七、深入 Diff 算法
❓10. Vue 3 是如何优化 v-for 列表的 diff 性能的?关键点在哪?
答案:
Vue 3 中的 patchKeyedChildren 实现了高性能 diff,关键在:

四端对比法:从头、尾同时比较新旧节点

key 查找优化:建立新节点的 key 映射表,加快查找

最长递增子序列(LIS)算法:用于判断哪些节点可以复用,避免不必要的移动操作

核心目标是通过 O(n) 时间复杂度实现最小 DOM 操作集合。

如果你有特定方向(如 Composition API 深挖、调度系统 Scheduler、Suspense 等),我可以继续给你出更深入的源码级问题。需要的话随时告诉我!

🧩 Vue 3 API 实现原理总览
Vue 3 源码主要分布在以下几个核心包中:

包名 说明
@vue/reactivity 响应式系统 API(如 ref, reactive)
@vue/runtime-core 组件系统、生命周期、渲染逻辑
@vue/runtime-dom DOM 渲染器(浏览器特有)
@vue/compiler-core 模板编译核心
@vue/shared 公共工具函数

一、响应式 API(来自 @vue/reactivity)
✅ 1. reactive() 实现原理
ts
复制
编辑
function reactive(target) {
return createReactiveObject(target, isReadonly = false, baseHandlers)
}
使用 Proxy 拦截对象

内部通过 get, set, deleteProperty 实现依赖收集和触发

使用 targetMap: WeakMap 存储依赖映射(target -> key -> effects)

✅ 2. ref() 实现原理
ts
复制
编辑
class RefImpl {
get value() { track(this, ‘value’); return this._value }
set value(newVal) { this._value = newVal; trigger(this, ‘value’) }
}
适合基本类型

用 .value 包装值并提供响应式访问

内部也使用 track / trigger

✅ 3. computed() 实现原理
本质上是带 lazy 标记的 effect

缓存值,依赖变化时重新计算

effect(() => getter(), {
lazy: true,
scheduler: () => markDirty()
})
✅ 4. watch() 与 watchEffect()
watchEffect:立即执行副作用,自动追踪依赖

watch:可配置 source、deep、immediate 等选项

底层使用:

effect(fn, { scheduler: job => queueJob(job) })
二、组件 API 与生命周期(runtime-core)
✅ 5. defineComponent()
用于类型提示,在运行时是一个透传函数

将组件包装为标准格式 { setup, render, … }

✅ 6. setup() 机制
setup() 在组件初始化时执行

返回对象会通过 proxyRefs() 自动解包为代理

返回函数则作为 render 使用

✅ 7. 生命周期钩子如 onMounted, onUpdated 实现原理
生命周期钩子本质是注册到当前组件实例的生命周期队列中:

export function onMounted(fn) {
injectHook(‘mounted’, fn, currentInstance)
}
这些钩子在组件 setup() 时通过 currentInstance 保存,在后续生命周期阶段统一调用。

三、渲染函数相关 API
✅ 8. h() 函数
创建一个标准化的 VNode

function h(type, props, children) {
return createVNode(type, props, children)
}
type 可以是组件、HTML 标签、Fragment、Comment 等

✅ 9. render() 函数
渲染 VNode 到容器,入口:

render(vnode, container)
内部调用 patch() 递归渲染,依类型分支执行不同逻辑(元素、组件、文本)

✅ 10. createApp() 实现原理

function createApp(rootComponent) {
return {
mount(container) {
const vnode = createVNode(rootComponent)
render(vnode, container)
}
}
}
包装根组件 vnode

调用 render 渲染到容器上

支持多实例 app,隔离上下文

四、模板编译相关 API(compiler-core)
Vue 模板编译分三阶段:

阶段 描述
Parse 将模板字符串转为 AST
Transform 优化 AST(静态提升、合并表达式)
Codegen 输出 JS 渲染函数

如:

html
复制
编辑

{{ msg }}

最终生成类似:

function render(_ctx, _cache) {
return createVNode(“p”, null, toDisplayString(_ctx.msg))
}
五、内置指令与模块(runtime-dom)
✅ 11. 指令 v-model 实现原理
在编译阶段转为 modelValue prop + onUpdate:modelValue 事件

运行时由组件或平台指令处理输入/输出双向绑定逻辑

el.value = modelValue
el.addEventListener(‘input’, e => emit(‘update:modelValue’, e.target.value))
✅ 12. v-bind, v-on, v-if, v-for 实现
全部在编译阶段处理成 render 函数

编译器生成不同的 patchFlag,用于运行时优化

例如 v-if:

isShow ? h(‘div’, …) : null
六、其它高级 API
✅ 13. provide() / inject()
使用组件实例内部的 provides 对象实现“原型链继承”

instance.provides = Object.create(parent.provides)
provide(key, value) 存入当前实例

inject(key) 通过原型链查找父组件的 provide

✅ 14. Teleport、Suspense、KeepAlive 等内置组件
都是特殊类型的组件,在 patch 阶段使用不同的处理逻辑

如 Teleport:

processTeleport(n1, n2, container, …)
🧠 总结
类型 API 原理关键点
响应式 reactive, ref, computed, watch Proxy 实现,track/trigger 管理依赖
组件系统 setup, defineComponent, 生命周期 组件实例管理生命周期与响应式绑定
渲染机制 h, render, patch VNode 虚拟树递归对比与渲染
编译相关 模板编译生成 render 函数 模板 AST -> 渲染函数
其他 provide/inject, Teleport 实例上下文机制与特殊处理流程

如果你希望我针对某一类 API(如响应式、生命周期或编译阶段)深入挖掘源码路径、源码调用链、调度机制,我可以继续扩展。需要我分模块讲得更细吗?

🔧 模块 1:响应式系统(@vue/reactivity)
1.1 核心结构概览
核心模块 文件
响应式入口 reactive.ts, ref.ts
依赖收集 effect.ts
追踪触发 track.ts, trigger.ts
工具函数 baseHandlers.ts, reactiveEffect.ts

1.2 reactive() 实现流程

export function reactive(target) {
return createReactiveObject(target, false, mutableHandlers)
}
➡️ createReactiveObject:

创建 Proxy

使用 mutableHandlers 拦截 get/set

设置在 reactiveMap 中做缓存

➡️ 响应式触发流程:

读取值 → get() → track(target, key)

修改值 → set() → trigger(target, key)

1.3 effect() 与依赖收集

effect(fn, options)
会包装为 ReactiveEffect 实例,保存到当前 activeEffect

在执行 fn 时,遇到响应式数据的 get() 会收集依赖

在 trigger 阶段,从 targetMap 中找到 effect 列表执行

1.4 ref() / computed() 实现精讲

class RefImpl {
get value() { track(this, ‘value’); return this._value }
set value(v) { this._value = v; trigger(this, ‘value’) }
}
ref 是响应式包裹器,适用于基本类型

computed 是懒加载、带缓存的 effect

🧱 模块 2:组件系统与运行时(@vue/runtime-core)
2.1 组件创建与挂载流程
关键函数: createVNode() → render() → patch() → mountComponent()

🔸 创建组件 VNode:

createVNode(Component, props)
🔸 挂载组件:

function mountComponent(initialVNode, container, parentComponent) {
const instance = createComponentInstance(initialVNode, parentComponent)
setupComponent(instance)
setupRenderEffect(instance, container)
}
2.2 setup() 执行流程
执行前设置 currentInstance

允许返回:

对象 → 绑定到 ctx

函数 → 用作 render

返回的响应式对象通过 proxyRefs() 自动展开 .value

2.3 生命周期钩子注册与执行

export function onMounted(fn) {
injectHook(‘mounted’, fn, currentInstance)
}
生命周期钩子存储在 instance.lifecycle 中

组件生命周期阶段由 setupRenderEffect() 控制调度调用

2.4 defineComponent() 与类型系统
并不影响运行时

是为 TS 推导服务的辅助函数

export function defineComponent(options) {
return options
}
🎨 模块 3:模板编译(@vue/compiler-core)
3.1 编译阶段流程

compile(template, options)
=> parse() → transform() → generate()
🔸 parse.ts:解析 HTML 为 AST
html
复制
编辑

{{ msg }}
转为:

{ type: ‘Interpolation’, content: ‘msg’ }
🔸 transform.ts:AST 转换优化
静态提升(hoist)

插值优化

合并表达式

🔸 codegen.ts:生成代码字符串

return function render(ctx) {
return createVNode(‘div’, null, toDisplayString(ctx.msg))
}
🖼️ 模块 4:渲染与虚拟 DOM(runtime-core + runtime-dom)
4.1 渲染流程

render(vnode, container)
内部调用 patch() 比较 vnode

根据 vnode 类型调用对应处理器(元素、组件、Fragment、Text)

4.2 Diff 算法优化点
使用 4 端比较法 + keyMap + 最长递增子序列

提高节点重用率,降低移动成本

🚚 模块 5:平台特有模块(@vue/runtime-dom)
5.1 DOM 渲染器封装

createRenderer(options)
浏览器平台实现 patchProp, insert, remove, setElementText

实现平台无关性(可适配 Native、WebGL 等)

5.2 指令处理(v-model / v-show 等)

const directive = {
beforeMount(el, binding) { … },
updated(el, binding) { … }
}
编译阶段识别 v-model → 运行时使用 patchDOMProp 设置事件监听

🧪 模块 6:高级 API(teleport / suspense / keep-alive)
6.1 Teleport:跨 DOM 树渲染
VNode 类型为 TELEPORT

patch 阶段调用 processTeleport() 将子节点渲染到目标容器

6.2 KeepAlive:缓存组件实例
使用 LRU 缓存

复用旧实例,跳过创建 & 挂载阶段

6.3 Suspense:异步组件占位符处理
提供 pending, resolve, fallback 逻辑

异步组件 resolve 前不渲染主内容

🧭 模块 7:公共工具与辅助函数(@vue/shared)
isObject, isArray, hasOwn, makeMap

ShapeFlags, PatchFlags 常量

编译 & 运行时共享函数

✅ 总结:各模块职责对照表
模块 文件包 关键职责
响应式 @vue/reactivity ref、reactive、effect、computed
组件系统 @vue/runtime-core 组件实例、生命周期、setup/render
渲染平台 @vue/runtime-dom DOM 相关渲染器、平台 API
编译器 @vue/compiler-core 模板字符串 -> 渲染函数
特殊组件 @vue/runtime-core keep-alive、suspense、teleport
工具函数 @vue/shared 工具函数和枚举常量

是否希望我进一步提供每个模块的 源码函数跳转路径(比如从 reactive() 进入到 createReactiveObject(),再进入 mutableHandlers.get() 具体实现)?或者针对某一类进行源码级逐行解析?可以按需细化 👇

您说:
vue所有api的使用场景,以及对应的场景面试题目,我需要很详细

ChatGPT 说:
非常好,这里我将为你整理 Vue 3 所有核心 API 的使用场景、对应源码机制简析,并为每个 API 设计一个针对性面试题目(含答题要点),帮助你进行系统性理解与模拟答题训练。

🧩 Vue 3 API 使用场景与面试题(详细完整版)
以下按照模块进行划分,包括响应式、组件、生命周期、渲染、工具等。

🔧 一、响应式系统 API
API 使用场景 面试题 答题要点
reactive() 对复杂对象进行响应式包装 ✅ Vue 3 中 reactive 是如何实现的?使用时有哪些注意点? 使用 Proxy;嵌套深层自动递归代理;不能代理原始类型;track/trigger 系统实现
ref() 包装基本类型(string/number)使其响应式;或用于 DOM/组件引用 ✅ ref 与 reactive 的区别及其适用场景? ref 适用于基本类型;.value 存取;内部封装 RefImpl
computed() 派生值缓存;依赖变化时重新计算 ✅ computed 是如何实现懒计算与缓存机制的? 使用 lazy effect;首次访问触发执行;内部使用 dirty 标志
watch() 监听响应式数据的变化并执行副作用逻辑 ✅ watch 和 watchEffect 有什么区别? watch 明确指定源;默认懒执行;可指定 flush;watchEffect 自动收集依赖
watchEffect() 自动依赖收集的响应式副作用,适合页面通用逻辑监听 ✅ watchEffect 什么时候执行?会监听哪些依赖? 立即执行;内部自动调用 effect;依赖变化触发重新运行
shallowReactive() 创建浅层代理,避免深度递归提升性能 ✅ shallowReactive 的作用及使用时机? 对象顶层响应式;深层属性不是响应式
customRef() 自定义 getter/setter 逻辑,控制依赖追踪/触发 ✅ 如何用 customRef 实现一个节流响应? 返回带 track/trigger 的自定义包装器;手动控制响应更新时机
readonly() 创建只读的响应式对象 ✅ readonly 和 reactive 的区别是?是否可以被修改? 创建只读 Proxy;set 时控制台报警;内部使用 readonlyHandlers
toRefs() 将响应式对象的属性变成 ref,常用于 setup() 返回值解构 ✅ 为什么在 setup 返回对象时推荐使用 toRefs()? 保留响应性;避免解构丢失响应

🧱 二、组件与 Composition API
API 使用场景 面试题 答题要点
defineComponent() 组件定义时获得更好的类型推导支持 ✅ defineComponent 有什么作用?是运行时函数吗? 类型辅助工具函数;不影响运行时逻辑
setup() 函数组件入口;响应式逻辑、生命周期注册都在其中完成 ✅ setup 的执行时机与作用?返回什么? 在 beforeCreate 之前;返回对象 → context,返回函数 → render
getCurrentInstance() 获取当前组件实例(开发工具、注册状态等) ✅ getCurrentInstance 应该在什么地方使用? 只能在 setup 或 生命周期钩子 中使用
provide / inject 实现跨层级依赖注入(如:主题、全局配置) ✅ provide/inject 是如何实现组件通信的? 通过组件实例 provides 链;inject 会查找父组件上下文
defineAsyncComponent() 定义异步组件(支持 loading/error 组件) ✅ 异步组件在 Vue 3 中是如何加载与错误处理的? 支持 loader、loadingComponent、timeout、errorComponent 等配置项
isRef / isReactive 判断某变量是否是 ref 或响应式对象 ✅ 判断响应式对象类型有哪些方法? isRef()、isReactive()、isProxy()

⏱️ 三、生命周期 API
API 使用场景 面试题 答题要点
onMounted() DOM 渲染后执行逻辑,如绑定事件或请求 ✅ 生命周期钩子在 setup 中是如何注册并触发的? 注入到实例对应的钩子数组中,在组件挂载/更新阶段统一调用
onUpdated() 数据更新后执行逻辑 ✅ 在哪个阶段访问的 DOM 一定是最新的? onUpdated;组件完成更新之后
onUnmounted() 清理资源(如:定时器、事件) ✅ 如何防止组件卸载时内存泄漏? onUnmounted 中清除副作用逻辑
onBeforeMount() 等 准确控制生命周期过程 ✅ onBeforeMount 和 onMounted 的执行顺序? 挂载前后;setup 中注册的钩子按声明顺序注册

🖼️ 四、渲染与模板相关
API 使用场景 面试题 答题要点
h() 手动创建 vnode,如 JSX、动态组件场景 ✅ h() 函数的参数结构是什么?最终返回什么? (type, props?, children?);返回 VNode 对象
createApp() 创建独立应用实例,支持多实例 ✅ createApp 支持哪些方法?和 Vue 2 有哪些不同? 提供挂载、插件注册、全局组件等方法
render() 手动渲染 vnode(如 Teleport、自定义容器) ✅ render() 是如何将 VNode 渲染到容器上的? 通过 patch 遍历 vnode 树并挂载到 DOM
Teleport DOM 跨层级渲染(如:弹窗) ✅ Teleport 的实现机制?如何指定目标容器? 类型为 TELEPORT; render 到指定 DOM 容器
KeepAlive 缓存组件实例(如:tab 页面切换) ✅ 如何缓存组件状态?KeepAlive 的作用? 保留组件状态不被卸载;支持 include/exclude
Suspense 渲染异步组件时提供 fallback ✅ Suspense 的工作原理?在哪些场景适用? 异步组件渲染前先展示 fallback;等待所有 async setup 完成后替换内容

🛠️ 五、工具函数类
API 使用场景 面试题 答题要点
markRaw() 阻止对象被代理(如大型类库对象) ✅ 如何避免某对象被 Vue 变成响应式? 使用 markRaw;或放入 shallowReactive 外层
toRaw() 获取原始对象 ✅ 如何获取 reactive 对象的原始值? toRaw(obj);但慎用,会丢失响应性
unref() 兼容普通值和 ref;常用于模板/工具函数中 ✅ unref 在模板中有用吗? 有用;如 unref(props.count);自动解包 ref
proxyRefs() 把对象中 ref 自动展开为值访问 ✅ proxyRefs 是如何优化模板解构的? 访问自动加 .value;修改自动转 ref.value

📦 六、编译器(运行时配合)
场景 使用 API 面试题 答题要点
模板编译 compile() ✅ Vue 模板编译过程包括哪几步? parse → transform → codegen;最终生成 render 函数
动态指令 v-bind、v-on ✅ v-bind 如何转化为 prop 赋值? 编译阶段转为 vnode props;运行时设置 DOM 属性或组件 props
动态组件 component + is ✅ 动态组件渲染过程是怎样的? 通过 渲染不同组件 VNode
v-model 编译 modelValue + update:modelValue ✅ Vue 3 中 v-model 编译后生成了什么? 双向绑定 props 和 emit;支持多个 v-model 绑定名

🧠 Bonus:实战类面试题(综合考查)
✅ 1. 如何实现一个带缓存功能的搜索组件?
👉 应用:ref + watch + debounce + cache Map

✅ 2. 在组件 setup() 中使用 ref 解构对象属性后失去响应性,该如何处理?
👉 答案:使用 toRefs() 或 reactive + 结构展开

✅ 3. 如何在组件内部优雅实现节流的输入监听?
👉 使用 watchEffect + customRef

✅ 总结
我们从使用场景出发,为 Vue 3 的 50+ 核心 API 设计了面试题,每一题都围绕实战、源码原理和使用注意事项展开。你可以按模块系统性训练。

如果你希望我将这些面试题汇总成文档或导出为面试题清单 PDF、markdown 文件,或想深入某个模块(如响应式依赖调度、patch diff、异步组件加载)我可以为你定制深入讲解。是否需要我继续细化某一类?

🔧 模块一:响应式依赖收集(Dep 收集)
✅ 概念讲解:
Vue 3 使用 Proxy 重写了 Vue 2 中基于 Object.defineProperty 的响应式系统。

核心模块:

@vue/reactivity

effect.ts:响应式副作用函数

reactive.ts:创建响应式对象

track.ts / trigger.ts:依赖收集与触发更新

effect(() => {
console.log(state.count) // 自动收集依赖
})
依赖是如何收集的?

访问响应式属性时触发 get → 执行 track() → 记录当前活跃的 effect。

修改属性时触发 set → 执行 trigger() → 找到所有依赖这个属性的副作用函数重新执行。

🎯 面试题 1:
Q:Vue 3 中是如何进行依赖收集的?effect 与 reactive 如何协作?

答:
Vue 3 使用了 Proxy 拦截 get 与 set 操作。当 effect 函数执行时,设置为 activeEffect。读取响应式属性时会通过 track() 将 target -> key -> Set 记录在 targetMap 中。属性变更后通过 trigger() 查找并重新执行依赖该属性的 effect。

🔧 模块二:响应式对象的实现 reactive/ref/toRef 等
✅ 概念讲解:
reactive(obj):深层响应式对象

ref(val):单值响应式

toRef(obj, key):保持属性响应式

readonly():只读代理

🎯 面试题 2:
Q:ref 和 reactive 有什么区别?分别适用于什么场景?

答:

ref 适用于基本类型或需要 .value 解包的响应式处理。

reactive 适用于对象或数组,自动递归深度响应式。

实际开发中,ref 常用于组合式 API 处理局部状态或基本类型状态,reactive 适用于表单对象、组件状态等结构性更复杂的数据。

🔧 模块三:调度与更新机制(Scheduler & Job Queue)
✅ 概念讲解:
在 effect 中可以指定一个 scheduler 函数,Vue 利用这个机制进行批处理。

ts
复制
编辑
effect(fn, {
scheduler: (job) => queueJob(job)
})
Vue 通过一个微任务队列 queueJob 实现合并更新,避免多次无效 DOM 操作。

🎯 面试题 3:
Q:Vue 是如何避免重复执行 effect 的?微任务队列在这里起了什么作用?

答:
Vue 会将带 scheduler 的副作用函数加入到一个 Set 队列中,使用 Promise.then() 作为微任务调度,在下一个微任务周期内统一执行。使用 Set 可以自动去重。

🔧 模块四:嵌套依赖追踪与优化
✅ 概念讲解:
嵌套 effect 可能导致依赖记录出错,Vue 使用一个栈 effectStack 管理当前活跃的 effect。

function effect(fn) {
const e = () => {
cleanup(e)
activeEffect = e
effectStack.push(e)
fn()
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
}
🎯 面试题 4:
Q:Vue 如何处理嵌套 effect 场景?

答:
通过维护一个 effectStack 栈来避免 effect 被误记录。进入 effect 时入栈,执行完出栈,恢复上一个活跃的 effect。

🔧 模块五:依赖清理与 stop 功能
✅ 概念讲解:
为了防止内存泄露,Vue 在每次 effect 执行前会清理旧的依赖。

ts
复制
编辑
function cleanup(effect) {
effect.deps.forEach(dep => dep.delete(effect))
effect.deps.length = 0
}
也支持 stop(effect) 停止某个响应式副作用追踪。

🎯 面试题 5:
Q:为什么 Vue 要清除旧的依赖?stop() 的原理是什么?

答:
effect 中依赖可能会变化(如分支判断),为了避免冗余追踪和错误更新,需要在重新执行前移除旧依赖。stop() 则是提前清除依赖,并防止 effect 被再次追踪。

🧠 场景题:模拟面试官提问风格
Q6:我现在在组件中用了 reactive 对象和多个 ref,每个都绑定在表单控件上,修改任意控件都会触发 render。你能讲讲这是如何实现的吗?能否避免频繁 render?

答:
每个响应式属性都有对应的依赖,当这些属性在组件 render 中被访问,会收集依赖(track)。一旦属性变更,trigger 会通知 effect 调度更新组件。可以通过:

watch 局部监听

shallowReactive 控制深度

debounce 合理延迟更新
来降低频繁 render 的问题。

✅ 总结:可扩展的面试提纲
模块 涉及 API 推荐面试题 源码文件
响应式核心 reactive, ref, track, trigger Vue 响应式原理及依赖收集机制 reactivity/reactive.ts, effect.ts
调度更新 effect scheduler Job Queue 如何调度更新? scheduler.ts
嵌套 effect 多层依赖追踪 Vue 如何处理嵌套副作用函数? effect.ts
依赖清理 stop, cleanup 为什么要清理依赖? effect.ts
实践场景 表单、组合式 API 如何管理复杂响应式状态? 综合应用


网站公告

今日签到

点亮在社区的每一天
去签到