2.6W 字 Vue 经典面试题源码级详解(整理与更新版)
原文是一篇覆盖面很广的 Vue 面试题合集。本文保留原来的 23 个主题、回答思路和源码分析方向,删除课程推广、抓取噪声和失效链接,并补充 Vue 2.7、Vue 3.5、Vue Router 4/5、Pinia 及 Nuxt 4 语境。源码片段均为教学化简化代码,不应当当作可直接替换的框架源码。
01 Vue 组件之间有哪些通信方式
可以先按组件关系回答,再补充特殊场景:
| 关系 | 常用方式 |
|---|---|
| 父子 | props、emits、v-model、模板 ref、$attrs |
| 跨层级 | provide/inject、状态管理、组合式函数 |
| 兄弟或跨模块 | 共同父状态、Pinia、明确的事件/消息通道 |
| DOM 或组件实例能力 | 模板 ref,尽量只暴露必要方法 |

1. props / emits
父组件通过 props 下传,子组件通过事件通知父组件:
<!-- Parent.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import Child from './Child.vue'
const message = ref('Hello')
</script>
<template>
<Child
:message="message"
@update:message="message = $event"
/>
</template>
<!-- Child.vue -->
<script setup lang="ts">
const props = defineProps<{ message: string }>()
const emit = defineEmits<{
'update:message': [value: string]
}>()
function update() {
emit('update:message', `${props.message}!`)
}
</script>
<template>
<button @click="update">{{ message }}</button>
</template>
实际使用时可以直接写成:
<Child v-model:message="message" />
2. provide/inject
适合主题、表单上下文、依赖服务等跨层级依赖,不适合把所有业务状态都藏进注入对象:
// provider
provide('formContext', { validate })
// descendant
const form = inject<{ validate: () => Promise<boolean> }>('formContext')
3. 需要避免的旧 API
Vue 2 常见的 $parent、$children、$root 和 event bus 可以在旧项目中维护,但它们会产生隐式依赖。Vue 3 已移除 $on、$off、$once、$children 和 $listeners;$attrs 仍然存在,但 Vue 3 已把未声明的监听器纳入 $attrs。
复杂应用中的共享状态优先使用 Pinia。Nuxt 4 应遵循其状态管理约定,并注意 SSR 中不能让请求之间共享可变用户状态。
02 v-if 和 v-for 哪个优先级更高
实践结论:不要把 v-if 和 v-for 写在同一个元素上。
版本差异:
- Vue 2:同一元素上
v-for优先级高于v-if,会先遍历再判断; - Vue 3:
v-if优先级高于v-for,所以v-if表达式不能直接访问该元素中由v-for声明的变量。

过滤列表:使用 computed
<script setup lang="ts">
import { computed, ref } from 'vue'
const users = ref([
{ id: 1, name: 'Ada', active: true },
{ id: 2, name: 'Grace', active: false },
])
const activeUsers = computed(() => {
return users.value.filter(user => user.active)
})
</script>
<template>
<li v-for="user in activeUsers" :key="user.id">
{{ user.name }}
</li>
</template>
控制整组列表:移动到容器
<ul v-if="shouldShowUsers">
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
如果不方便添加真实容器,也可以使用 <template v-if> 包住列表。即使两个指令在某个版本能够运行,也不代表这是可读、可维护或高效的写法。
03 Vue 生命周期以及每个阶段做什么
生命周期钩子是组件在创建、挂载、更新和卸载等阶段提供的扩展点。
| Vue 2.7 | Vue 3.5 | 说明 |
|---|---|---|
beforeCreate | beforeCreate | 实例选项初始化早期 |
created | created | Options API 状态和事件已初始化 |
beforeMount | onBeforeMount | 首次 DOM 挂载前 |
mounted | onMounted | 组件 DOM 已插入 |
beforeUpdate | onBeforeUpdate | 响应式更新提交前 |
updated | onUpdated | DOM 更新后 |
beforeDestroy | onBeforeUnmount | 卸载前 |
destroyed | onUnmounted | 卸载完成 |
activated | onActivated | KeepAlive 缓存组件激活 |
deactivated | onDeactivated | KeepAlive 缓存组件停用 |
errorCaptured | onErrorCaptured | 捕获后代组件错误 |
serverPrefetch | onServerPrefetch | SSR 服务端预取,需对应 SSR 环境 |
Vue 3 还提供 onRenderTracked 和 onRenderTriggered 调试钩子。它们不是普通业务生命周期,不应在生产逻辑中依赖。

常见使用场景
created或setup:准备状态、创建非 DOM 依赖;mounted:访问浏览器 DOM、初始化第三方 DOM 库;updated:确实需要在组件更新后操作 DOM 时使用,避免在其中再次无条件修改状态;beforeUnmount/onBeforeUnmount:取消订阅、移除事件、停止定时器;unmounted/onUnmounted:释放最终资源;onActivated/onDeactivated:恢复或暂停缓存页面的轮询。
setup() 在 Vue 3 创建阶段执行,通常早于 Options API 的 created,但它不是一个可以简单替换所有 created 逻辑的同名钩子。SSR 不会执行 onMounted,因为服务端不存在浏览器 DOM。
04 Vue 的双向绑定使用和原理
v-model 可以理解为:
Model → View:属性/响应式渲染
View → Model:input/change 等事件
原生表单元素
<script setup lang="ts">
import { ref } from 'vue'
const message = ref('')
const checked = ref(false)
const selected = ref('vue')
</script>
<template>
<input v-model="message">
<input v-model="checked" type="checkbox">
<select v-model="selected">
<option value="vue">Vue</option>
<option value="react">React</option>
</select>
</template>
Vue 会根据元素类型使用不同的 property 和事件:文本输入通常对应 value/input,checkbox 对应 checked/change,select 对应 value/change。.lazy、.number、.trim 可以改变同步时机或值处理,但 .number 不是严格类型验证。
自定义组件
Vue 3 默认展开为:
<CustomInput
:model-value="message"
@update:model-value="message = $event"
/>
组件内部:
<script setup lang="ts">
const props = defineProps<{ modelValue: string }>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
function onInput(event: Event) {
emit('update:modelValue', (event.target as HTMLInputElement).value)
}
</script>
<template>
<input
:value="props.modelValue"
@input="onInput"
>
</template>
Vue 3.4+ 可以使用 defineModel() 简化组件写法;Vue 2 默认是 value prop 和 input 事件,也可以通过组件 model 选项修改名称。Vue 3 支持 v-model:title 等多个绑定参数,.sync 不再是推荐的组件通信方式。
05 如何扩展一个 Vue 组件
可以按“逻辑复用”和“内容复用”回答:
- Composition API/composable:Vue 3 首选的逻辑复用方式;
- mixins:Vue 2 历史项目常见,但来源不透明、容易命名冲突;
extends:Options API 级别的配置合并,使用较少;- slots:内容分发,不等同于逻辑复用;
- render function / higher-order component:特殊组件库场景使用。
composable
// useCounter.ts
import { computed, ref } from 'vue'
export function useCounter() {
const count = ref(0)
const double = computed(() => count.value * 2)
const increment = () => count.value++
return { count, double, increment }
}
<script setup lang="ts">
import { useCounter } from './useCounter'
const { count, double, increment } = useCounter()
</script>
slots
<!-- Child.vue -->
<template>
<section class="card">
<header><slot name="title">默认标题</slot></header>
<main><slot /></main>
</section>
</template>
<Child>
<template #title>用户列表</template>
<UserList />
</Child>
Vue.extend 是 Vue 2 的构造器 API,Vue 3 已移除;不能把 Vue 2 的 Vue.extend 代码直接迁移到 Vue 3。Vue 3 应使用 defineComponent、h、composable 或普通组件组合。
06 子组件可以直接改变父组件的数据吗
原则上不应该。props 是父向子的单向下行绑定,子组件不应直接改写 prop:
<script setup lang="ts">
const props = defineProps<{ value: string }>()
// ❌ props.value = 'new value':只读 prop,Vue 会警告
</script>
正确做法是 emit 事件,由父组件修改状态:
<script setup lang="ts">
const props = defineProps<{ value: string }>()
const emit = defineEmits<{ change: [value: string] }>()
function changeValue(value: string) {
emit('change', value)
}
</script>
如果 prop 只是初始值,可以复制到本地 ref;如果只是格式化,用 computed:
const props = defineProps<{ initialCount: number, size: string }>()
const count = ref(props.initialCount)
const normalizedSize = computed(() => props.size.trim().toLowerCase())
对象和数组 prop 的嵌套属性在 JavaScript 上仍可能被子组件修改,因为引用是共享的;Vue 不能完全阻止这种行为,但应把它视为隐式耦合,优先通过 emit、store action 或更新整个对象来保持数据流清晰。
07 Vue 权限管理如何做到按钮级别
权限通常分成:
- 页面/路由权限:用户能否进入某个路由;
- 操作权限:页面中能否看到或使用某个按钮;
- 服务端资源权限:API 最终是否允许操作。
前端权限控制只能改善导航和展示,不能作为安全边界。用户可以绕过隐藏按钮直接请求 API,后端必须再次验证角色、权限和资源归属。
路由权限
Vue Router 4/5 可以在路由 meta 中声明权限:
const routes = [
{
path: '/users',
component: () => import('./views/UsersView.vue'),
meta: { permissions: ['user:read'] },
},
]
守卫推荐返回 true、false 或重定向位置:
router.beforeEach(to => {
if (!to.meta.permissions?.length) return true
if (auth.hasAll(to.meta.permissions)) return true
return { name: 'forbidden' }
})
Vue Router 3 的旧项目仍常见 next 回调,但要保证每条路径只调用一次。
服务端返回路由时,前端不能直接把不可信的字符串当作任意组件 import。应使用组件白名单映射:
const componentMap = {
Users: () => import('./views/UsersView.vue'),
Reports: () => import('./views/ReportsView.vue'),
}
Router 3 使用 router.addRoutes(),Router 4/5 使用 router.addRoute();后者一次添加一个路由记录或嵌套路由。动态添加后如果当前地址需要重新匹配,可以调用 router.replace(router.currentRoute.value.fullPath)。
按钮权限
可以用 v-permission 控制展示,但需要监听权限异步更新,不要只在首次 mounted 时永久删除节点。权限指令的完整实现可参考 69-分享8个非常实用的Vue自定义指令。

08 如何理解 Vue 响应式
响应式是:状态被读取时记录依赖,状态变化时通知依赖并执行相应更新。
Vue 2.7
- 普通对象:
Object.defineProperty的 getter/setter; - 依赖:
Dep收集Watcher; - 数组:拦截部分变更方法;
- 新增/删除属性:需要
Vue.set/this.$set、Vue.delete等兼容 API; - Map/Set:不具备 Vue 3 那样完整的原生集合响应式支持。
Vue 3.5
- reactive 对象:Proxy/Reflect;
- 基础值:
ref; - 派生值:
computed; - 副作用:
watch/watchEffect; - 集合:对 Map/Set 等操作提供专门的依赖处理;
- 调度:scheduler 批量安排组件和 watcher 更新。
Proxy 改善了新增属性、删除属性、数组索引和集合等操作的覆盖范围,但它不能完整 polyfill 到 IE11,也不是没有运行时成本。大对象、不可变数据和外部库实例有时适合 shallowRef 或 markRaw。
09 如何理解虚拟 DOM
VNode 是描述 UI 的 JavaScript 对象。模板经 compiler 编译为 render function,render function 返回 VNode,renderer 再将 VNode 挂载或 patch 到宿主环境:

Virtual DOM 的价值包括:
- 声明式描述 UI,减少手动维护 DOM;
- 统一处理节点复用、属性更新、插入、移动和删除;
- 配合 scheduler 批量更新;
- 通过不同 renderer 支持 SSR 或自定义平台。
但不能说 Virtual DOM 天生更快,也不能说它保证最小 DOM 操作。VNode 创建、Diff 和调度本身有成本;Vue 3 的性能还来自静态提升、Patch Flags、Block Tree 和 compiler-informed rendering。详情可参阅 62-你对虚拟DOM原理的理解。
10 了解 Diff 算法吗
Diff/patch 的作用是比较新旧渲染结果,并把必要的变化提交给 renderer。常见假设是:
- 主要做同层比较,不做任意跨层树编辑;
- 不同节点类型通常替换子树;
- key 用来表达列表节点身份;
- 编译器尽量跳过静态内容。
Vue 2.7
Vue 2 的 updateChildren 使用双端指针,尝试比较:
- 旧头/新头;
- 旧尾/新尾;
- 旧头/新尾;
- 旧尾/新头;
- 都不匹配时通过 key map 查找、移动或创建节点。

Vue 3.5
Vue 3 的 keyed children 更新会先处理相同前缀和后缀,再建立 key 到新索引的映射,处理新增、删除和移动;需要移动时会计算最长递增子序列(LIS),尽量保留已经按新顺序排列的节点。
“Diff 算法是 O(n),所以一定快”是不完整的。算法复杂度只是模型,真实性能还受 VNode 数量、组件更新范围、属性 patch、布局/绘制、事件和业务计算影响。
11 Vue 3/3.5 有哪些新特性
API 与组件能力
- Composition API:
ref、reactive、computed、watch; <script setup>;- Fragment:组件可以有多个根节点;
Teleport;Suspense:异步依赖和 fallback,部分高级能力仍需结合版本/框架验证;defineAsyncComponent;emits声明;defineModel(Vue 3.4+);useTemplateRef、onWatcherCleanup等后续 API;- 自定义 renderer;
- CSS
v-bind()。

框架层面的改进
- Proxy 响应式和独立 reactivity 包;
- compiler 的静态提升、Patch Flags、Block Tree;
- 更好的 tree-shaking;
- TypeScript 重写和模块化;
- Composition API 更适合按逻辑组织大型组件。
“Vue 3 更快、更小”是相对于对应版本和构建方式的总体方向,不应理解为任何页面都自动更快。Suspense、SSR、hydration 和 Nuxt 的行为还要看具体框架版本。
12 如何定义动态路由并获取参数
Vue Router 4/5:
const routes = [
{
path: '/users/:id',
component: () => import('./views/UserView.vue'),
},
{
path: '/posts/:username/:postId',
component: () => import('./views/PostView.vue'),
},
]
在 Composition API 中:
<script setup lang="ts">
import { watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
watch(
() => route.params.id,
id => loadUser(String(id)),
{ immediate: true },
)
</script>
Options API 使用 this.$route.params.id。查询参数和 hash 分别在 route.query、route.hash 中。路由组件在同一路径记录下切换参数时通常会复用实例,不会重新执行完整 mounted,因此要 watch 参数或使用路由守卫。
捕获 404 可以把通配路由放在合适位置:
{ path: '/:pathMatch(.*)*', component: NotFoundView }
Vue Router 3 的写法是 path: '*',不能把两种版本语法混用。
13 从零实现一个简易 Vue 路由
路由器需要解决:URL 改变但页面不整页刷新,并根据当前地址渲染匹配组件。
基本设计
- 保存 routes 配置;
- 根据 hash 或 history 读取当前 location;
- 监听
hashchange或popstate; - 匹配路由记录;
- 提供
RouterLink和RouterView; - 处理 base、404、嵌套路由和导航守卫。
现代 Vue Router 使用:
import {
createRouter,
createWebHistory,
} from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
})
hash 模式使用 createWebHashHistory(),不需要服务器配置 fallback;history 模式需要服务器把未知前端路径回退到 index.html,否则刷新深层 URL 可能返回 404。
RouterLink 负责导航,RouterView 根据当前匹配记录渲染组件。真正的路由还要处理编码、动态参数、query、嵌套、滚动行为、异步组件和导航守卫,所以手写 demo 只适合理解原理。
Vue Router 4/5 的守卫推荐返回值:
router.beforeEach(to => {
if (to.meta.requiresAuth && !auth.isLoggedIn()) {
return { name: 'login', query: { redirect: to.fullPath } }
}
return true
})
14 key 的作用
key 用于表达 VNode/列表项目的身份,帮助 renderer 判断旧节点和新节点是否代表同一个实体:
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
列表发生插入、删除、排序时,稳定 key 可以让 Vue 找到对应旧节点,尽量复用并移动 DOM;没有 key 时,Vue 可以采用就地 patch,按位置复用节点。无 key 不等于“永远认为任意两个节点相同”,节点类型和上下文仍然参与判断。
不要把数组 index 当成所有列表的 key。静态、只追加且不重排的列表可能没有明显问题,但可插入、删除、排序或含输入状态的列表应使用业务 ID。
key 也可用于强制重建组件:
<Editor :key="documentId" :document="document" />
同一标签过渡切换不同内容时设置不同 key,可以让 Transition 识别为不同节点。key 必须是稳定的字符串或数字,不能使用每次 render 都变化的随机值。



15 nextTick 的使用和原理
Vue 会把响应式更新放入调度队列并批量刷新。修改状态后,如果同一段代码要读取更新后的 DOM,可以使用:
import { nextTick, ref } from 'vue'
const count = ref(0)
const element = ref<HTMLElement | null>(null)
async function increment() {
count.value++
await nextTick()
console.log(element.value?.textContent)
}
Vue 2 使用 this.$nextTick(),Vue 3 可使用 await nextTick()。它等待的是 Vue 当前的 DOM flush,不等于 setTimeout(0)、Node 的 process.nextTick,也不保证浏览器已经完成绘制。需要下一次渲染机会时再结合 requestAnimationFrame。
Vue 3 scheduler 会维护组件更新 job、pre/post flush callbacks 和当前 flush Promise;Vue 2.7 有 callbacks 队列和环境回退。源码细节随 minor 版本变化,不要把旧版本的 flushSchedulerQueue 名称套到 Vue 3。
16 watch 和 computed 的区别与选择
computed:从状态得到派生值,默认惰性求值并缓存;watch:监听明确 source,执行异步请求、日志、缓存或第三方同步等副作用;watchEffect:立即执行并自动追踪同步读取的依赖。
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
watch(
() => route.params.id,
id => loadArticle(String(id)),
{ immediate: true },
)
要“算出一个值”优先 computed,要“变化后做事情”使用 watch。computed getter 应保持纯净,watch 返回停止监听函数;Vue 3.5 可使用 onWatcherCleanup 取消过期异步请求。Vue 2.7 的 Dep/Watcher 和 Vue 3.5 的 effect/scheduler 不能混为一谈。
17 父子组件创建和挂载顺序
在首次同步挂载、没有异步组件/KeepAlive/特殊 renderer 的前提下,常见顺序是:
父 beforeCreate / created / beforeMount
→ 子 beforeCreate / created / beforeMount
→ 子 mounted
→ 父 mounted
Vue 3 的 setup() 在组件创建早期执行,Options API 的 created 语义不能直接等同于 setup。更新时通常是父 beforeUpdate → 子 beforeUpdate → 子 updated → 父 updated,但实际会受组件是否更新、scheduler 和异步 watcher 影响。
异步组件未加载完成时,父组件可能先 mounted,子组件在 loader 完成后再挂载;<Suspense> 可以改变异步依赖的提交时机。不要把这个顺序当作跨所有场景的通信协议。
18 如何缓存组件,缓存后如何更新
使用内置 <KeepAlive>:
<KeepAlive :include="['UserList']" :max="10">
<component :is="activeComponent" />
</KeepAlive>
它缓存组件实例,组件切换出去通常触发 deactivated,切回来触发 activated,不会再次 mounted。只有被淘汰或父级卸载时才真正销毁。
Vue Router 4/5 应缓存 RouterView slot 中的实际路由组件:
<RouterView v-slot="{ Component }">
<KeepAlive :max="10">
<component :is="Component" />
</KeepAlive>
</RouterView>
页面需要重新获取数据时,可以在 onActivated 中刷新,也可以 watch 路由参数:
onActivated(() => {
loadData()
})
watch(() => route.params.id, () => loadData(), { immediate: true })
include/exclude 匹配组件 name,max 通常按最近最少使用策略淘汰。KeepAlive 是内存和重复初始化之间的取舍,不应缓存所有页面。
19 从零构建一个 Vue 项目
现代 Vue 3 项目通常使用 Vite/create-vue:
pnpm create vue@latest
pnpm install
pnpm dev
可按项目需要选择:
- Vue Router:路由;
- Pinia:共享状态;
- Vitest、Vue Test Utils、Playwright:测试;
- ESLint、Prettier、TypeScript:质量和类型;
vueuse:经过验证的组合式工具;- UI 组件库;
- Axios 或原生 fetch 封装;
- commitlint、lint-staged、husky:提交检查。
不要为了“完整”把所有依赖都加入项目。Nuxt 4 项目应遵循 Nuxt 的 app/、server/、public/、路由和自动导入约定,不要照搬纯 Vite 项目的目录。
一个普通 Vite Vue 项目可以是:
src/
├── api/
├── assets/
├── components/
├── composables/
├── layouts/
├── router/
├── stores/
├── utils/
├── views/
├── App.vue
└── main.ts
public 适合无需构建处理的静态资源,src/assets 适合需要被构建器处理的资源;具体目录应服务于边界,而不是为了凑结构。
20 实际工作中的 Vue 最佳实践
编码与可维护性
- 组件名使用多词,避免与原生 HTML 元素冲突;
- props、emits 和 slots 明确声明并提供类型;
- 使用稳定 key,不把
v-if与v-for写在同一元素; - 逻辑复用优先 composable,避免大型 mixin;
- 组件边界按更新频率和业务职责划分;
- 让表单、路由和异步状态有明确的 loading/error/empty 状态。
性能
- 路由和大组件按需加载;
- 大列表使用虚拟列表、分页或服务端筛选;
- 稳定 props,避免无意义的父子更新;
- 对大型不可变数据考虑
shallowRef/markRaw; - 使用
v-once前确认内容永远不变; - 图片压缩、响应式图片、预加载和懒加载要结合 LCP;
- 用 Performance 面板和 RUM 测量 LCP、INP、CLS,不要只凭感觉。
安全与可访问性
- 不使用用户输入拼接模板;
- 谨慎使用
v-html、动态 URL 和动态 style; - 用户内容必须在合适的边界清洗;
- 交互控件优先使用语义元素,保证键盘焦点和 ARIA;
- 权限必须由服务端校验,前端隐藏按钮不是安全措施。
21 如何理解 Vuex
Vuex 是集中式状态管理库,核心概念包括 state、getters、mutations 和 actions。它适合维护 Vue 2/4 时期的历史项目:
import { createStore } from 'vuex'
const store = createStore({
state: () => ({ count: 0 }),
getters: {
double: state => state.count * 2,
},
mutations: {
increment(state) {
state.count++
},
},
actions: {
increment({ commit }) {
commit('increment')
},
},
})
Vuex 3 对应 Vue 2,Vuex 4 对应 Vue 3;新 Vue 3 项目通常优先 Pinia。Pinia 直接使用 Vue 响应式,提供 state/getters/actions、插件和更好的 TypeScript 体验,没有强制 mutations:
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: state => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
简单应用可以用 composable + reactive,不要因为“共享状态”就把所有局部状态塞进全局 store。SSR 应按请求隔离状态,避免用户 A 的数据泄露给用户 B。

22 从 template 到 render 的处理过程
Vue compiler 的核心职责是把 template 转成可执行的 render function:
template
↓ parse
AST
↓ transform / 优化
带动态信息的 AST
↓ codegen
render function
Vue 3 编译器会分析静态节点、动态属性和 block,生成类似这样的结果:
import { createElementVNode as _createElementVNode } from 'vue'
function render(_ctx, _cache) {
return _createElementVNode('p', null, _ctx.message)
}
真实编译结果会包含 openBlock、createElementBlock、Patch Flags 和 hoisted nodes。.vue 文件还会经过 SFC 编译器处理 <script setup>、<template> 和 <style scoped>。
运行时构建和 Runtime + Compiler 构建的区别在于:前者不在浏览器中编译字符串 template,模板通常在构建阶段编译;后者包含 compiler,能够在运行时编译,但包体积更大。React 也有 JSX/TSX 转换工具和编译优化,但“有没有编译器”不能作为 Vue 与 React 的绝对区别。
23 Vue 实例挂载过程中发生了什么
Vue 3 的入口通常是:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
可以从两个方面回答:
- 创建和初始化组件实例:解析 props、slots、setup、状态、provide/inject、生命周期和渲染上下文;
- 建立渲染更新机制:创建组件 render effect,首次执行 render 得到 VNode,renderer mount 到 DOM;后续响应式状态触发 effect scheduler,重新 render 并 patch。
首次挂载的简化链路:
createApp
↓
创建根组件实例
↓
执行 setup / 初始化状态
↓
创建组件更新 effect
↓
首次 render 得到 VNode
↓
patch(null, vnode, container)
↓
真实 DOM 插入并触发 mounted
如果是 SSR 客户端接管,使用 createSSRApp().mount() 进行 hydration,而不是从空容器重新创建整棵 DOM。hydration 要求服务端和客户端初始渲染结果一致,否则会出现 hydration mismatch。
总结
这篇合集的面试回答可以归纳为:
- 组件通信优先 props/emits,跨层级使用 provide/inject 或 Pinia;
computed产出派生值,watch/watchEffect处理副作用;- Vue 2 的
defineProperty/Dep/Watcher与 Vue 3 的Proxy/effect/scheduler要分开回答; - VNode、Diff、key 和 compiler 优化共同组成渲染更新链路,Virtual DOM 不保证所有场景更快;
- Router 3 的
addRoutes/next、Vue 2 的生命周期和组件 v-model 不能直接套到 Router 4/5、Vue 3; - KeepAlive 缓存实例,RouterView slot、activated/deactivated 和内存淘汰需要一起考虑;
- 前端权限、输入过滤和 v-html 都不能替代服务端安全边界;
- 面试源码题应先给公开 API 和行为,再说明版本相关的内部实现。
参考资料
- Vue 3 官方指南
- Vue 3 响应式深入
- Vue 3 渲染机制
- Vue 3 生命周期
- Vue 3 组件通信
- Vue 3
v-model - Vue Router 动态路由
- Vue Router 动态添加路由
- Vue Router 导航守卫
- Pinia 核心概念
- Vue 3 KeepAlive
- Vue 3 性能最佳实践
- Vue 2.7 响应式原理
原文作者:杨村长。原合集的 23 个面试主题、回答结构和实战方向予以保留;宣传内容、失效抓取链接、重复段落、损坏代码块及 Vue 2/Vue 3、Router 3/4/5 混淆表述已整理。