高级前端开发者必会的 34 道 Vue 面试题系列
原文件实际拼接了四篇旧文章:MVVM 与 Vue 响应式、Vue 2/3 数组监听、Vue 2 异步渲染与
nextTick、Vue 2 生命周期。本文保留这些文章的学习路线和源码关键词,重新整理为 34 道问题,删除抓取噪声、损坏代码和推广内容,并补充 Vue 3.5、Vue Router 4/5、Pinia、SSR 与 Nuxt 4 的现代说法。

1. MVC 和 MVVM 分别是什么
MVC 和 MVVM 都是组织代码职责的思想,不是某一个框架的专属实现。
MVC
- Model:数据模型、领域数据和数据处理;
- View:展示数据和接收用户输入的界面;
- Controller:协调用户操作、数据请求、模型变更和视图更新。
MVVM
- Model:业务数据;
- View:界面;
- ViewModel:连接 Model 和 View 的状态与绑定层。
Vue 经常被称为 MVVM 风格框架,是因为组件状态与模板之间存在响应式绑定,但 Vue 官方并不要求开发者严格按经典 MVVM 三层拆分项目。Vue 的组件、composable、store 和 renderer 共同构成了现代应用结构。

2. MVC 中 Model、View、Controller 各负责什么
以一个不依赖框架的页面为例:
<div>
<span id="name"></span>
<div id="data"></div>
</div>
<script>
// Model:模拟接口或数据服务返回的数据
function getData() {
return {
name: 'mvc',
data: 'mvc 数据信息',
}
}
// Controller:获取数据并决定如何更新页面
function renderPage() {
const result = getData()
document.querySelector('#name').textContent = `姓名:${result.name}`
document.querySelector('#data').textContent = result.data
}
renderPage()
</script>
MVC 在前后端分离后并没有消失,只是观察角度发生了变化:可以把整个前端项目当作系统的 View,也可以把接口返回的 JSON 当作 Model,把前端状态处理和页面渲染当作 Controller/View。不要把“前后端分离”理解成 MVC 彻底过时。
3. Vue 为什么常被认为是 MVVM 的实现
在理想的绑定模型中,Model 发生变化,View 自动反映变化;用户在 View 中输入,也能通过事件更新状态:
Model ↔ ViewModel ↔ View
早期 AngularJS 使用脏检查,Vue 2 使用 Object.defineProperty,Vue 3 使用 Proxy 和响应式 effect。这些都是实现绑定的不同技术。
但 Vue 并不是一个要求所有业务都遵循经典 MVVM 的完整架构。Vue 组件通常仍然采用单向数据流:父组件通过 props 传值,子组件通过 emits 通知;v-model 是这一协议的语法糖,而不是子组件直接修改父组件状态。

4. Vue 2 的 new Vue() 初始化做了什么
Vue 2 的简化初始化流程如下:
new Vue(options)
→ _init(options)
→ initLifecycle / initEvents / initRender
→ beforeCreate
→ initInjections / initState / initProvide
→ created
→ $mount
→ beforeMount / render watcher / mounted
initState 会处理 props、methods、data、computed 和 watch。data 对象会交给 Observer 转换为响应式数据,普通属性通过 Object.defineProperty 定义 getter/setter。
原文把“new Vue 阶段”简单写成 Observer(vm.data),真实过程还包含实例代理、依赖注入、生命周期、事件和渲染初始化,不能只用一个 Observer 概括。

5. Vue 3 的应用初始化有什么不同
Vue 3 通过 createApp 创建应用:
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.use(router)
app.use(pinia)
app.mount('#app')
每个应用实例拥有自己的应用上下文,不再依赖 Vue 2 的全局构造函数。组件在 setup() 或 <script setup> 中声明状态、computed、watch、生命周期和依赖。
Vue 3 的响应式对象由 Proxy 代理,组件渲染通常由一个 ReactiveEffect 驱动,更新任务交给 scheduler 批处理。Vue 2 的 Observer、Dep、Watcher 仍有助于理解旧项目源码,但不是 Vue 3 的完整内部模型。
6. Vue 2 响应式中的 Observer、Dep 和 Watcher 是什么
Observer
递归遍历对象,为属性定义 getter/setter;对于数组则增强七个变更方法,并观察数组元素中的对象。
Dep
依赖容器。getter 读取数据时收集当前 watcher,setter 触发时通知订阅者。
Watcher
执行某种观察任务的对象,常见有:
- render watcher:重新执行 render 并更新组件;
- computed watcher:惰性计算并缓存 computed;
- user watcher:执行
watch回调。
简化的 Dep:
class Dep {
static target = null
constructor() {
this.subs = []
}
depend() {
if (Dep.target) Dep.target.addDep(this)
}
notify() {
for (const watcher of this.subs.slice()) {
watcher.update()
}
}
}
依赖收集不是“页面加载时把所有 data 都订阅一次”,而是渲染 watcher 执行 render 时,读取了哪些响应式属性,就建立哪些依赖关系。
7. Vue 2 如何通过 getter/setter 收集依赖和派发更新
可以把一个响应式属性简化为:
function defineReactive(target, key, value) {
const dep = new Dep()
let currentValue = value
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get() {
dep.depend()
return currentValue
},
set(nextValue) {
if (Object.is(nextValue, currentValue)) return
currentValue = nextValue
dep.notify()
},
})
}
完整 Vue 2 还会:
- 对嵌套对象继续 observe;
- 通过 child observer 的 dep 收集数组依赖;
- 处理新值的响应式转换;
- 触发 scheduler 去重和批量更新;
- 在组件更新后调用
updated等钩子。
所以常见的面试回答是“getter 收集依赖,setter 派发更新”,但还应补充 watcher 和异步队列,否则答案不完整。
8. Vue 2 为什么对新增属性和数组下标有局限
Object.defineProperty 只能拦截已经定义的属性。Vue 2 中直接添加对象属性可能不会响应:
this.user.name = 'Vue' // 如果 name 初始不存在,可能无法响应
this.$set(this.user, 'name', 'Vue')
数组直接用下标赋值或修改 length 也有局限:
this.list[0] = item
this.list.length = 0
应该使用:
this.$set(this.list, 0, item)
this.list.splice(0, this.list.length)
Vue 3 中通常可以直接:
state.list[0] = item
state.list.length = 0
state.user.name = 'Vue'
但这不意味着 Vue 2 完全不能观察数组元素。已经被定义响应式的元素属性可以触发 setter;问题主要是越界新索引、length 和原生方法没有经过 Vue 2 的依赖通知。


9. Vue 2 为什么要改写数组方法
Vue 2 会创建一个继承自 Array.prototype 的 arrayMethods,改写以下七个变更方法:
const arrayMethods = Object.create(Array.prototype)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse',
]
简化的 push/splice 拦截逻辑:
methodsToPatch.forEach(method => {
const original = Array.prototype[method]
arrayMethods[method] = function mutator(...args) {
const result = original.apply(this, args)
const observer = this.__ob__
let inserted
if (method === 'push' || method === 'unshift') {
inserted = args
} else if (method === 'splice') {
inserted = args.slice(2)
}
if (inserted) observer.observeArray(inserted)
observer.dep.notify()
return result
}
})
这不是因为 JavaScript 完全不能监听数组,而是 Vue 2 没有给所有可能的数组索引都定义 getter/setter,转而拦截有限的变更方法。数组中的对象元素仍会被递归 observe,但“替换某个索引”与“修改对象元素内部属性”是两个不同问题。

10. Vue 3 的 Proxy 如何处理对象和数组
Proxy 可以在对象代理层拦截 get、set、deleteProperty、has 和 ownKeys 等操作,因此 Vue 3 能够更自然地追踪:
- 新增和删除对象属性;
- 数组下标赋值;
- 数组 length 变化;
- Map/Set 的方法和迭代;
- 嵌套对象的懒代理。
一个教学版示例:
const targetMap = new WeakMap()
let activeEffect
function track(target, key) {
if (!activeEffect) return
let deps = targetMap.get(target)
if (!deps) {
deps = new Map()
targetMap.set(target, deps)
}
let dep = deps.get(key)
if (!dep) {
dep = new Set()
deps.set(key, dep)
}
dep.add(activeEffect)
}
function trigger(target, key) {
targetMap.get(target)?.get(key)?.forEach(effect => effect())
}
function reactive(target) {
return new Proxy(target, {
get(object, key, receiver) {
const value = Reflect.get(object, key, receiver)
track(object, key)
return value
},
set(object, key, value, receiver) {
const oldValue = object[key]
const result = Reflect.set(object, key, value, receiver)
if (!Object.is(oldValue, value)) trigger(object, key)
return result
},
})
}
真实 Vue 3 还需要区分新增与修改、处理数组 length、避免重复代理、管理 effect scope 和 scheduler。教学代码不能替代 Vue 的响应式实现。

11. Proxy 和 Reflect 分别解决什么问题
Proxy 是代理对象操作的机制,Reflect 是与这些内部操作对应的标准方法集合。handler 中使用 Reflect,可以正确传递 receiver、继承关系和返回值:
const raw = { name: 'Vue' }
const proxy = new Proxy(raw, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver)
},
})
原文用 setPrototypeOf 演示返回值时有拼写和逻辑噪声。正确理解是:Reflect.setPrototypeOf(proxy, proto) 返回布尔值;如果 Proxy 的 setPrototypeOf trap 返回 false,Object.setPrototypeOf(proxy, proto) 在严格语义下可能抛出 TypeError,而 Reflect 版本直接返回 false,更适合在业务代码中检查结果。
Reflect 不是 Vue 专属,也不会自动产生响应式;只有 Vue 的 Proxy handler 在调用 Reflect 前后配合 track/trigger,才形成响应式效果。

12. Vue 的 computed 为什么有缓存
computed 适合表达纯派生状态:
const firstName = ref('Ada')
const lastName = ref('Lovelace')
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
它的核心特点:
- 第一次读取时求值;
- 记录 getter 读取的响应式依赖;
- 依赖没有变化时直接返回缓存;
- 依赖变化后标记为 dirty;
- 下一次读取时重新求值。
Vue 2 通过 lazy computed watcher 和 dirty 实现,Vue 3 通过带 effect 的 computed ref 实现。computed getter 不应发请求、修改其他状态或依赖随机数;这些行为应放到 watch 或事件处理函数中。
13. watch 和 watchEffect 有什么区别
watch(source, (value, oldValue) => {
// 明确监听 source,适合需要新旧值、取消请求和精确控制的场景。
})
watchEffect(() => {
// 自动收集同步执行期间读取的响应式依赖。
})
watch需要明确数据源,默认惰性执行;watchEffect会立即运行,并自动收集依赖;- 深层对象、数组、清理、flush 时机都要根据场景选择;
- Vue 3.5 可以使用
onWatcherCleanup清理过期请求。
import { onWatcherCleanup, watch } from 'vue'
watch(id, async currentId => {
const controller = new AbortController()
onWatcherCleanup(() => controller.abort())
const response = await fetch(`/api/${currentId}`, {
signal: controller.signal,
})
})
onWatcherCleanup 必须在 watcher 回调的同步阶段注册,不能等异步 await 之后才注册。
14. nextTick 是什么
Vue 的响应式状态改变后,组件更新通常会被放进 scheduler 队列,而不是同步修改 DOM:
<script setup>
import { nextTick, ref } from 'vue'
const message = ref('old')
const node = ref<HTMLElement | null>(null)
async function update() {
message.value = 'new'
console.log(node.value?.textContent) // 可能还是 old
await nextTick()
console.log(node.value?.textContent) // new
}
</script>
<template>
<p ref="node">{{ message }}</p>
<button @click="update">更新</button>
</template>
nextTick 适合等待 Vue 当前 DOM 更新完成,不等于等待图片、网络请求或浏览器下一次绘制。如果只是 watcher 需要在 DOM 更新后执行,也可以考虑 flush: 'post'。
15. Vue 2 的异步更新队列如何工作
Vue 2 中一次状态更新的典型链路是:
响应式 setter
→ dep.notify()
→ watcher.update()
→ queueWatcher(watcher)
→ nextTick(flushSchedulerQueue)
→ watcher.run()
→ vm._render()
→ vm._update()
→ patch
queueWatcher 会通过 watcher id 去重和排序。这样在同一段同步代码中:
this.value = 1
this.value = 2
this.value = 3
通常只需要把最终状态 3 渲染到页面,而不必同步提交三次中间 DOM 更新。

16. Vue 2 的 nextTick 如何选择异步 API
Vue 2 的 nextTick 会维护 callback 队列,并根据环境选择调度方式,历史上大致是:
Promise.then
→ MutationObserver
→ setImmediate
→ setTimeout
这是 Vue 2 时代的兼容策略,不应写成所有浏览器永远固定的顺序。简化实现:
const callbacks = []
let pending = false
function flushCallbacks() {
pending = false
const copies = callbacks.slice()
callbacks.length = 0
copies.forEach(callback => callback())
}
function nextTick(callback) {
callbacks.push(callback)
if (pending) return
pending = true
Promise.resolve().then(flushCallbacks)
}
真实源码还会处理异常、Promise 返回值和旧环境兼容。原文中代码被抓取成一行,不能直接复制运行;上面的代码只用于表达队列关系。

17. Vue 3 的 scheduler 和 nextTick 有什么不同
Vue 3 使用 job queue、pre-flush callbacks 和 post-flush callbacks 管理更新。nextTick 的核心可以理解为:
const resolvedPromise = Promise.resolve()
let currentFlushPromise: Promise<void> | null = null
function nextTick(callback?: () => void) {
const promise = currentFlushPromise || resolvedPromise
return callback ? promise.then(callback) : promise
}
组件更新任务执行时,Vue 会合并同一个 job 的重复触发;nextTick 会等待当前 flush promise。Vue 3 不再沿用 Vue 2 的 Watcher 类实现,但“响应式依赖触发更新,scheduler 批量执行,最后 patch”的总体思想仍然相近。

18. 为什么要批量异步更新
批量更新有两个主要原因:
- 性能:多次同步修改可能只需要一次最终渲染;
- 体验:避免把中间状态短暂展示出来造成闪烁。
但“异步渲染”不是说浏览器一定等一个 setTimeout 才绘制,也不是说所有更新都延迟到宏任务。Vue 使用 scheduler 和微任务/宏任务策略协调更新,最终绘制还受浏览器渲染时机影响。
如果同步代码中有长时间的 while 循环,浏览器主线程会被阻塞,Vue 的微任务也无法执行:
state.value = 'new'
const start = Date.now()
while (Date.now() - start < 3000) {
// 阻塞主线程,页面无法在此期间绘制
}
这不是 Vue 更新失败,而是 JavaScript 主线程被占用。耗时任务应拆分、移入 Web Worker 或采用增量计算。




19. Vue 能不能同步更新
Vue 2 有 Vue.config.async = false 和内部 watcher 的 sync 路径,主要用于调试或特殊场景;它们会破坏默认批处理,不建议在业务代码中全局开启。
Vue 3 可以对单个 watcher 指定:
watch(source, callback, { flush: 'sync' })
但同步 watcher 在批量数组修改、递归状态变化和高频输入中可能造成大量执行。需要读取更新后的 DOM 时,正确做法通常是 await nextTick(),而不是强行让全部渲染同步。
20. Vue Router 解决什么问题
路由器维护 URL 与组件视图之间的映射,在 SPA 中负责:
- 匹配路径和动态参数;
- 更新当前 route 状态;
- 加载异步组件;
- 执行导航守卫;
- 处理历史记录和滚动行为;
- 渲染
router-view,生成router-link。
Vue Router 3 面向 Vue 2,Vue Router 4/5 面向 Vue 3。Nuxt 4 使用文件系统路由,但底层仍然依赖 Vue Router 生态能力。
21. hash、history 和 memory 模式有什么区别
hash
http://example.com/#/users
改变 hash 通常不会向服务器发送新的文档请求,部署简单,但 URL 中包含 #。
history
使用 pushState、replaceState 和 popstate:
const router = createRouter({
history: createWebHistory(),
routes,
})
URL 更自然,但服务器必须把 /users 等前端路径回退到 index.html;否则刷新深层 URL 会得到 404。
memory
在测试、SSR 或没有浏览器地址栏的环境使用内存 history,不依赖真实 URL。
22. Vue Router 的导航守卫有哪些
全局守卫
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !isLoggedIn()) {
return { name: 'login' }
}
})
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
await askForCameraPermission()
}
})
router.afterEach((to, from, failure) => {
if (!failure) analytics.page(to.fullPath)
})
路由独享守卫
const routes = [
{
path: '/admin',
component: AdminPage,
beforeEnter: () => {
return isAdmin() || '/403'
},
},
]
组件内守卫
Vue Router 3/4/5 可以使用 Options API 的 beforeRouteLeave、beforeRouteUpdate、beforeRouteEnter;Vue 3 还可以在 setup 中使用 onBeforeRouteLeave 和 onBeforeRouteUpdate。
现代守卫推荐返回 false、重定向位置或 true,旧版 next 回调仍可兼容,但必须确保每条分支只调用一次。
23. 完整的导航解析过程怎样记
可以按以下顺序回答:
- 导航被触发;
- 失活组件的
beforeRouteLeave; - 全局
beforeEach; - 重用组件的
beforeRouteUpdate; - 路由记录的
beforeEnter; - 解析异步路由组件;
- 被激活组件的
beforeRouteEnter; - 全局
beforeResolve; - 导航确认;
- 全局
afterEach; - DOM 更新;
- Vue Router 3 的
beforeRouteEnter回调在实例创建后执行。
如果任意可取消守卫返回 false 或新的位置,导航会被取消或重定向;afterEach 不能取消已经确认的导航。
24. 如何理解 Vue 2 Router 的插件安装
Vue 2 使用:
Vue.use(VueRouter)
插件对象或函数会被安装一次。Vue Router 的 install 会注册全局组件、混入根实例识别逻辑,并把 $router、$route 暴露给组件。
一个教学版骨架:
let Vue
class MiniRouter {
constructor(options = {}) {
this.mode = options.mode || 'hash'
this.routes = options.routes || []
this.routeMap = this.routes.reduce((map, route) => {
map[route.path] = route.component
return map
}, Object.create(null))
}
}
MiniRouter.install = function (VueConstructor) {
Vue = VueConstructor
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
this._routerRoot = this
this._router = this.$options.router
} else {
this._routerRoot = this.$parent?._routerRoot
}
},
})
Object.defineProperty(Vue.prototype, '$router', {
get() {
return this._routerRoot?._router
},
})
}
完整路由器还要实现响应式当前路由、浏览器事件、守卫、组件加载、router-link 和 router-view,不能只返回一个固定的“首页”节点。
25. Vue Router 4/5 和 Vue 2 Router 的插件机制有什么变化
Vue 3 不再通过全局 Vue 构造函数安装插件,而是安装到应用实例:
const router = createRouter({
history: createWebHistory(),
routes,
})
const app = createApp(App)
app.use(router)
app.mount('#app')
路由器通过 app 的 provide/inject 和全局属性向组件提供能力。实际项目不建议自行修改 $router 或 $route,应使用 useRouter()、useRoute() 和官方组件。
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
router.push({ name: 'home' })
26. Vue 模板为什么需要编译
浏览器不理解 Vue 的 {{ name }}、v-if、v-for 和组件标签。构建工具会把 SFC 模板编译成 render function;使用 runtime-only 构建时,模板应该已经在构建阶段完成编译。
Vue 2 常见的源码流程是:
parse:模板字符串 → AST
optimize:标记静态节点
generate:AST → render 函数代码
原文说“模板不是正确 HTML,所以浏览器无法解析”过于简单。模板中包含 Vue 的扩展语法,浏览器可以解析其中的普通 HTML,但不能执行 Vue 指令;真正需要编译的是 Vue 模板语义。
27. Vue 3 编译器怎样帮助运行时优化
Vue 3 的 compiler-core 大致经历:
baseParse
→ transform
→ 指令/节点转换
→ generate
它可以生成:
- 静态提升的 vnode;
- patch flags,标记动态 text/class/style/props;
- Block Tree,收集动态子节点;
- 缓存事件处理器和静态属性;
- 针对
v-if、v-for、组件和插槽的运行时代码。
因此 Vue 3 的运行时不必把模板生成的整棵树当成完全未知的动态树处理。VDOM 的性能来自响应式粒度、编译器信息和 renderer 策略的配合,而不是“虚拟 DOM 天然更快”。
28. Vue 的生命周期是什么
生命周期描述组件从创建、初始化响应式状态、挂载、更新到卸载的过程。
Vue 2 Options API:
beforeCreate → created
beforeMount → mounted
beforeUpdate → updated
beforeDestroy → destroyed
Vue 3:
setup
onBeforeMount → onMounted
onBeforeUpdate → onUpdated
onBeforeUnmount → onUnmounted
Vue 3 把 Vue 2 的 beforeDestroy/destroyed 改名为 beforeUnmount/unmounted。setup() 执行时承担了很多原来 created 阶段的初始化职责。
29. beforeCreate、created、beforeMount、mounted 的区别
beforeCreate:实例初始化很早的阶段,data、methods 等还未完成;created:数据、computed、watch 等已初始化,通常可以做不依赖 DOM 的请求;beforeMount:组件即将进行首次挂载;mounted:同步子组件已挂载,组件 DOM 已插入,可以访问$el/ref。
Vue 3 Composition API 写法:
import { onBeforeMount, onMounted } from 'vue'
onBeforeMount(() => {
// 即将挂载
})
onMounted(() => {
// 可以访问组件 DOM
})
onMounted 不会在 SSR 服务端执行。




30. 更新阶段的 beforeUpdate 和 updated 有什么区别
beforeUpdate 在响应式状态变化、组件即将 patch 前调用,此时通常还可以读到旧 DOM;updated 在本轮 DOM 更新后调用。
import { onBeforeUpdate, onUpdated } from 'vue'
onBeforeUpdate(() => {
// 记录更新前的 DOM 状态
})
onUpdated(() => {
// 读取更新后的 DOM,但不要无条件改动自身依赖状态
})
对于某个点击事件后的 DOM 读取,await nextTick() 通常比把逻辑放进全局 updated 更容易理解。updated 中无条件修改状态可能形成更新循环。
同步父子组件常见的挂载顺序是:
父 beforeMount
子 beforeCreate → created → beforeMount → mounted
父 mounted
更新时通常是父 beforeUpdate、子更新、子 updated、父 updated,但具体还会受到更新来源、scheduler 和异步组件影响。


31. Ajax 请求应该放在哪个钩子
不能简单回答“永远放 mounted”。判断标准是请求是否依赖 DOM 和是否需要 SSR:
- Vue 2 不依赖 DOM 的请求常放在
created; - Vue 3 可以在 setup/composable 中初始化,也可以在
onBeforeMount前执行; - 依赖浏览器 API 或 DOM 的逻辑放在
onMounted; - Nuxt 4 需要 SSR、缓存和请求状态时使用
useFetch或useAsyncData; - 监听路由参数重新请求时使用
watch,并清理过期请求; - 服务端预取使用
onServerPrefetch或 Nuxt 数据获取能力。
请求本身通常不需要 DOM,所以把它延迟到 mounted 不一定更好;但 window、document、第三方图表实例等浏览器对象不能在 SSR 的 setup/created 中直接访问。
32. beforeDestroy/destroyed 和 beforeUnmount/unmounted 何时使用
卸载前适合清理组件创建的副作用:
setInterval/setTimeout;- window/document 事件;
- WebSocket、EventSource、BroadcastChannel;
- IntersectionObserver、ResizeObserver;
- 第三方图表、编辑器和播放器实例;
- 未完成请求和订阅。
Vue 2:
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
clearInterval(this.timer)
this.socket?.close()
}
Vue 3:
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
clearInterval(timer)
controller.abort()
})
destroyed/unmounted 执行时组件已从管理树中移除,不应再依赖其 DOM 仍然存在。Vue 2 使用 destroy 命名,Vue 3 使用 unmount 命名。

33. KeepAlive、errorCaptured 和 serverPrefetch 怎么理解
KeepAlive
被 <KeepAlive> 缓存的组件切换时通常触发 activated/deactivated,而不是每次都重新 mounted/unmounted:
import { onActivated, onDeactivated } from 'vue'
onActivated(() => {
// 恢复轮询或刷新可见数据
})
onDeactivated(() => {
// 暂停轮询和昂贵监听
})
缓存组件仍然会占用内存,可以通过 include、exclude 和 max 控制范围。
errorCaptured / onErrorCaptured
它们用于捕获后代组件的渲染、生命周期、事件处理等错误:
import { onErrorCaptured } from 'vue'
onErrorCaptured((error, instance, info) => {
reportError(error, info)
return false // 阻止继续向上级 errorCaptured 传播
})
它不是完整的全局错误监控替代品,生产环境还应配置 app.config.errorHandler 和统一上报。
serverPrefetch / onServerPrefetch
Vue 2.6 的 serverPrefetch 和 Vue 3 的 onServerPrefetch 只在 SSR 预取阶段有意义,通常返回 Promise。Nuxt 应优先使用其数据获取方案,以获得缓存、payload 注水和错误处理能力。


34. Vue 2 升级 Vue 3 时如何总结
可以按能力而不是按 API 名称机械替换:
| Vue 2 | Vue 3 |
|---|---|
new Vue() | createApp() |
Vue.use() | app.use() |
beforeDestroy/destroyed | beforeUnmount/unmounted |
Object.defineProperty 响应式 | Proxy + effect |
Vue.set/this.$set | 通常直接赋值,API 已移除 |
VueRouter 构造函数 | createRouter |
| Vuex 常见方案 | Pinia 常见方案 |
slot-scope | v-slot/# |
全局实例 $on/$off/$once | 移除,使用 props/emits 或独立 emitter |
面试中可以这样作答:
Vue 是渐进式声明式 UI 框架,核心是组件化、响应式和 renderer。Vue 2 通过
Object.defineProperty、Dep、Watcher 和数组方法改写实现响应式,Vue 3 通过 Proxy、track/trigger、ReactiveEffect 和 scheduler 实现。状态变化后 Vue 会批量调度组件更新,重新生成 vnode,再由 patch 提交 DOM 操作;Vue 3 编译器还会提供静态提升、patch flags 和 Block Tree。工程上我会结合 Vue Router、Pinia、composable 和 Nuxt 组织应用,并根据场景处理 SSR、请求清理、列表虚拟化和组件边界。对于旧项目保留 Vue 2 原理有助于维护,对于新项目优先采用 Vue 3.5 的现代 API。
参考资料
- Vue 3:Reactivity in Depth
- Vue 3:响应式 API
- Vue 3:
nextTick - Vue 3:Lifecycle Hooks
- Vue 3:Rendering Mechanism
- Vue 2:Reactivity in Depth
- Vue 2:Instance Lifecycle
- Vue Router:Navigation Guards
- Vue Router:History Modes
- Vue 3 迁移指南
- Nuxt 4:Data Fetching
- 原文:Vue 2.0 与 3.0 的 MVVM 实现
- 原文:Vue 2/3 的数据变化监听
- 原文:Vue 异步渲染与 nextTick
- 原文:Vue 生命周期
原文作者:全栈者。原文件中关于 MVC/MVVM、Vue 2/3 响应式、数组监听、异步渲染、nextTick、Vue Router 和生命周期的内容予以合并保留;损坏代码、抓取标记、错误拼写、招聘推广、过时的绝对结论和 Vue 2/3 混用内容已删除或修正。