vue3面试题
约 5596 字大约 19 分钟
2025-06-01
vue2 vue3.0 vue3.2 备注 beforeCreate setup 组件创建之前 可以获取顶级实例对象 created setup 组件创建完成,可以获取变量 beforeMount onBeforeMount 挂载前,VNdom创建完成,真实dom未渲染 mounted onMounted 挂载完成,真实dom创建完成,可以获取dom beforeUpdate onBeforeUpdate dom更新前触发 updated onUpdated dom更新完成触发 beforedestroy,destroyed beforeUnmount onBeforeUnmount 组件卸载后触发 所有的挂载的数据 子组件全部卸载后触发 errorCaptured onErrorCaptured 在捕获一个来自后代组件的错误时被调用 renderTracked onRenderTracked 跟踪虚拟 DOM 重新渲染时调用 renderTriggered onRenderTriggered 当虚拟 DOM 重新渲染被触发时调用 activated activated onActivated 缓存组件激活时调用 deactivated deactivated onDeactivated 缓存组件失活时调用

高频面试题
1.父子组件生命周期执行顺序
- 挂载阶段:
父onBeforeMount->子onBeforeMount->子onMounted->父onMounted
- 更新阶段:
父onBeforeUpdate->子onBeforeUpdate->子onUpdated->父onUpdated
- 卸载阶段:
父onBeforeUnmount->子onBeforeUnmount->子onUnmounted->父onUnmounted
2.在setup()中如何访问this?
- setup()中没有this,响应式数据通过ref/reactive定义,方法直接申明。
3.异步请求放在哪个钩子?
- 客户端渲染(CSR):onMounted(确保DOM可用)。
- 服务端渲染(SSR):onServerPrefetch。
2.vue3和vue2的区别
Vue 3 与 Vue 2 的核心区别主要体现在响应式系统、API 设计、性能优化及新特性等方面。以下是详细对比:
⚡ 1. 响应式系统
- Vue 2:
- 基于
Object.defineProperty
实现数据劫持 - 局限性:无法自动检测对象属性的动态添加/删除(需用
Vue.set
/Vue.delete
),对数组索引修改或长度变化监听不完善
- 基于
Vue 3:
- 改用
Proxy
代理整个对象,支持动态属性增删、嵌套对象及数组索引的直接监听
🧩 2. API 设计
- Vue 2(Options API):
- 逻辑分散在
data
、methods
、computed
等选项中,复杂组件易导致代码碎片化
- 逻辑分散在
Vue 3(Composition API):
- 引入
setup()
函数,通过ref
、reactive
等函数按功能组织代码,提升逻辑复用性和可维护性
✨ 4. 新特性
- 多根节点支持(Fragments):
- Vue 3 允许多个根元素,无需外层包裹
<div>
- Vue 3 允许多个根元素,无需外层包裹
v-model
改进:
- 支持多个
v-model
绑定(如v-model:title
、v-model:content
)
3.谈一谈对 MVVM 的理解?
MVVM是Model-View-ViewModel
缩写,也就是把MVC
中的Controller
演变成ViewModel
。Model层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。
4.v-model 双向绑定的原理是什么?
v-model
本质就是一个语法糖,可以看成是value + input
方法的语法糖。 可以通过model属性的prop
和event
属性来进行自定义。原生的v-model,会根据标签的不同生成不同的事件和属性 。
vue3 简化
v-model = :value + @update:属性名

5.vue2.x 和 vuex3.x 渲染器的 diff 算法分别说一下?
简单来说,diff算法有以下过程
- 同级比较,再比较子节点
- 先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
- 比较都有子节点的情况(核心diff)
- 递归比较子节点
正常Diff两个树的时间复杂度是O(n^3)
,但实际情况下我们很少会进行跨层级的移动DOM
,所以Vue将Diff进行了优化,从O(n^3) -> O(n)
,只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。
Vue2的核心Diff算法采用了双端比较
的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
vue3:Vue 3 在列表更新中引入 最长递增子序列(LIS)算法替代 Vue 2 的双端比较策略,显著降低了 Diff 算法的复杂度,优化了性能
6. 说一下 v-if 与 v-show 的区别
- v-show
- 作用: 控制元素显示隐藏
- 语法: v-show = "表达式" 表达式值为 true 显示, false 隐藏
- 原理: 切换 display:none 控制显示隐藏
- 场景:频繁切换显示隐藏的场景
- v-if
- 作用: 控制元素显示隐藏(条件渲染)
- 语法: v-if= "表达式" 表达式值 true显示, false 隐藏
- 原理: 基于条件判断,是否创建 或 移除元素节点
- 场景: 要么显示,要么隐藏,不频繁切换的场景
7. keep-alive的常用属性有哪些及实现原理
keep-alive
可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
常用的两个属性include/exclude
,允许组件有条件的进行缓存。
两个生命周期activated/deactivated
,用来得知当前组件是否处于活跃状态。
8. nextTick 的作用是什么?他的实现原理是什么?
在下次 DOM 更新循环结束之后执行延迟回调。nextTick主要使用了宏任务和微任务。根据执行环境分别尝试采用
- Promise
- MutationObserver
- setImmediate
- 如果以上都不行则采用setTimeout
定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。
9.说一下 Vue SSR 的实现原理
SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端
。
SSR有着更好的SEO、并且首屏加载速度更快等优点。不过它也有一些缺点,比如我们的开发条件会受到限制,服务器端渲染只支持beforeCreate
和created
两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境。还有就是服务器会有更大的负载需求。
10.Vue2 组件的 data 为什么必须是函数
个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数
。如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。
11.说一下 watch 与 computed 的区别是什么?以及他们的使用场景分别是什么?
- 都是观察数据变化的(相同)
- 计算属性将会混入到 vue 的实例中,所以需要监听自定义变量;watch 监听 data 、props 里面数据的变化;
- computed 有缓存,它依赖的值变了才会重新计算,watch 没有;
- watch 支持异步,computed 不支持;
- watch 是一对多(监听某一个值变化,执行对应操作);computed 是多对一(监听属性依赖于其他属性)
- watch 监听函数接收两个参数,第一个是最新值,第二个是输入之前的值;
- computed 属性是函数时,都有 get 和 set 方法,默认走 get 方法,get 必须有返回值(return)
watch 的 参数:
- deep:深度监听
- immediate :组件加载立即触发回调函数执行
computed 缓存原理:
conputed本质是一个惰性的观察者;当计算数据存在于 data 或者 props里时会被警告;
vue 初次运行会对 computed 属性做初始化处理(initComputed),初始化的时候会对每一个 computed 属性用 watcher 包装起来 ,这里面会生成一个 dirty 属性值为 true;然后执行 defineComputed 函数来计算,计算之后会将 dirty 值变为 false,这里会根据 dirty 值来判断是否需要重新计算;如果属性依赖的数据发生变化,computed 的 watcher 会把 dirty 变为 true,这样就会重新计算 computed 属性的值。
12. 说一下你知道的 vue 修饰符都有哪些?
1.什么是指令修饰符?
所谓指令修饰符就是通过“.”指明一些指令后缀 不同的后缀封装了不同的处理操作 —> 简化代码
1.事件修饰符
- stop:阻止事件冒泡。
js 体验AI代码助手 代码解读 复制代码<button @click.stop="handleClick">点击</button>
- prevent:阻止默认行为。
js 体验AI代码助手 代码解读 复制代码<form @submit.prevent="onSubmit"></form>
- capture:使用捕获模式。
js 体验AI代码助手 代码解读 复制代码<div @click.capture="handleCapture">捕获触发</div>
- self:仅当事件从元素自身触发时执行。
js 体验AI代码助手 代码解读 复制代码<div @click.self="handleSelf">仅自身点击有效</div
- once:事件只触发一次。
js 体验AI代码助手 代码解读 复制代码<button @click.once="handleOnce">只触发一次</button>
- passive:提升滚动性能,不阻止默认行为。
js 体验AI代码助手 代码解读 复制代码<div @scroll.passive="onScroll">滚动优化</div>
2.v-model修饰符
- lazy:输入框失焦后更新数据(替代input为change事件)。
js 体验AI代码助手 代码解读 复制代码<input v-model.lazy="message" />
- number:将输入值转为数值类型。
js 体验AI代码助手 代码解读 复制代码<input v-model.number="age" type="number" />
- trim:自动去除首位空格。
js 体验AI代码助手 代码解读 复制代码<input v-model.trim="username" />
3.键盘修饰符
- 键名修饰符:直接使用按键名(如:.enter,.tab,.esc)。
js 体验AI代码助手 代码解读 复制代码<input @keyup.enter="submit" />
- 系统修饰键:.ctrl,.alt,.shift,.meta(MAC的Command健)
js 体验AI代码助手 代码解读 复制代码<button @click.ctrl="handleCtrlClick">需按住 Ctrl 点击</button>
- .exact:精确匹配系统修饰键组合。
js 体验AI代码助手 代码解读 复制代码<button @click.ctrl.exact="onlyCtrl">仅 Ctrl 按下时触发</button>
4.鼠标修饰符
- .left,.right,.middle:限制鼠标按键。
js 体验AI代码助手 代码解读 复制代码<div @mousedown.right="handleRightClick">右键点击</div>
高频面试题
1.v-model的.lazy和.sync有何区别?
- .lazy:延迟数据同步(input->change事件)。
- .sync:Vue2中用于父子组件双向绑定,Vue3改用v-model:prop。
2.如何阻止事件冒泡和默认行为?
- 链式调用:
@click.stop.prevent
(顺序不影响效果)。
3.Vue3如何监听组件原生事件?
- 子组件手动绑定并emit事件
- 使用v-on="$attrs"将事件绑定到内部元素。
4.如何实现自定义v-model修饰符?
- 通过modelModifiers判断修饰符存在,并调整数据逻辑。
5..exact修饰符的作用是什么?
- 精确控制系统修饰键组合,避免其他修饰键按下触发。
自定义指令
1.注册方式
- 全局注册
js 体验AI代码助手 代码解读复制代码app.directive('focus', { mounted(el) { el.focus(); } });
- 局部注册(组合式API)
js 体验AI代码助手 代码解读复制代码<script setup> const vFocus = { mounted: (el) => el.focus() }; </script>
高频面试题
1.v-if和v-show的区别
- v-if:条件为假时销毁DOM,适合不频繁切换场景
- v-show:始终保留DOM,通过css切换显示,适合频繁切换
2.为什么v-for需要key?
- 虚拟DOM优化:key帮助Vue识别节点身份,避免错误使用复用元素,提升更新效率。
3.如何实现自定义指令?
- 定义指令对象并注册,通过生命周期钩子操作DOM(如自动聚焦、权限控制)。
4.v-model在自定义组件中的实现原理?
- 父组件:
v-model:propName="value"
- 子组件:接收propName,通过
emit('update:propName',value)
更新
5.Vue3中v-bind的合并策略变化
- Vue3中后绑定的属性会覆盖前面的同名属性,而Vue2会合并(如class和style)
13.如何实现 vue 项目中的性能优化?
编码阶段
- 尽量减少 data 中的数据,data 中的数据都会增加 getter 和 setter,会收集对应的 watcher
- v-if 和 v-for 不能连用
- 如果需要使用 v-for 给每项元素绑定事件时使用事件代理
- SPA 页面采用 keep-alive 缓存组件
- 在更多的情况下,使用 v-if 替代 v-show
- key 保证唯一
- 使用路由懒加载、异步组件
- 防抖、节流
- 第三方模块按需导入
- 长列表滚动到可视区域动态加载
- 图片懒加载
*SEO* 优化
- 预渲染
- 服务端渲染 SSR
打包优化
- 压缩代码
- Tree Shaking/Scope Hoisting
- 使用 cdn 加载第三方模块
- 多线程打包 happypack
- splitChunks 抽离公共文件
- sourceMap 优化
用户体验
- 骨架屏
- PWA
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启 gzip 压缩等。
14.Vue 中的 Key 的作用是什么?
*key* 的作用主要是为了高效的更新虚拟 *DOM* 。另外 vue 中在使用相同标签名元素的过渡切换时,也会使用到 key 属性,其目的也是为了让 vue 可以区分它们,否则 vue 只会替换其内部属性而不会触发过渡效果。
15.组件中写 name 选项有哪些好处
可以通过名字找到对应的组件( 递归组件:组件自身调用自身 )
可以通过 name 属性实现缓存功能(keep-alive)
可以通过 name 来识别组件(跨级组件通信时非常重要)
使用 vue-devtools 调试工具里显示的组见名称是由 vue 中组件 name 决定的
16.说一下 ref 的作用是什么?
ref 的作用是被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。其特点是:
- 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素
- 如果用在子组件上,引用就指向组件实例
所以常见的使用场景有:
- 基本用法,本页面获取 DOM 元素
- 获取子组件中的 data
- 调用子组件中的方法
17、ref和reactive的区别
核心区别与使用场景
特性 | ref | reactive |
---|---|---|
适用数据类型 | 基本类型(string /number /boolean )或对象 | 对象或数组 |
访问方式 | 通过 .value 访问 | 直接访问属性 |
响应式原理 | 内部对对象类型调用 reactive | 基于 Proxy 的深层代理 |
模板自动解包 | 在模板中无需 .value | 直接使用属性 |
解构响应性 | 需用 toRefs 保持响应性 | 直接解构会丢失响应性,需用 toRefs |
核心面试题
1.为什么ref需要.value?
- 设计目的:统一处理基本类型和对象类型。
- 基本类型无法通过Proxy代理,ref通过封装对象(
{ value:... }
)实现响应式
- 基本类型无法通过Proxy代理,ref通过封装对象(
- 底层实现:
js 体验AI代码助手 代码解读复制代码function ref(value) {
return {
__v_isRef: true,
get value() { track(this, 'value'); return value; },
set value(newVal) { value = newVal; trigger(this, 'value'); }
};
}
2.如何选择ref和reactive?
- 优先ref
- 管理基本类型数据
- 需要明确的数据引用(如传递到函数中仍保持响应性)
- 优先reactive
- 管理复杂对象/数组,避免频繁使用.value
- 需要深层嵌套的响应式数据
3.如何解构reactive对象且保持响应性?
- 使用toRefs
js 体验AI代码助手 代码解读复制代码const state = reactive({ count: 0, name: 'Vue' });
const { count, name } = toRefs(state); // 保持响应性
count.value++; // 生效
4.reactive的局限性是什么?
- 无法直接替换整个对象
js 体验AI代码助手 代码解读复制代码let obj = reactive({ a: 1 });
obj = { a: 2 }; // 响应式丢失!
- 解决方案:使用Object.assign或ref包裹对象
js 体验AI代码助手 代码解读复制代码Object.assign(obj, { a: 2 }); // 保持响应性
const objRef = ref({ a: 1 }); // 替换整个对象时响应式有效
5.ref如何处理对象类型
- 自动调用reactive
js 体验AI代码助手 代码解读复制代码const objRef = ref({ count: 0 });
objRef.value.count++; // 响应式生效
6.如何使用ref实现防抖功能?
- 自定义customRef
js 体验AI代码助手 代码解读复制代码function debouncedRef(value, delay = 200) {
let timeout;
return customRef((track, trigger) => ({
get(){
track();
return value;
}
set(newVal){
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newVal;
tigger();
}, delay);
}
}))
}
// 使用
const text = debouncedRef('', 500);
原理深入
ref的响应式实现
- 基本类型:通过Object.defineProperty实现value的getter/setter。
- 对象类型:内部转化为reactive代理
reactive的响应式实现
- 基于Proxy代理整个对象,递归处理嵌套熟悉
- 依赖收集:在get时调用track收集依赖
- 触发更新:在set时调用trigger通知更新
高频面试题
1.ref和reactive底层实现差异
- ref封装value属性,reactive使用Proxy代理整个对象
2.为什么解构reactive对象会失去响应性?
- 解构得到的是普通值,非响应式引用;使用toRefs转化为ref
3.如何在模版中正确使用ref?
- 直接使用变量名(自动解包.value),但嵌套在对象中需手动解包
js 体验AI代码助手 代码解读复制代码<template>
{{ count }} <!-- 自动解包 -->
{{ objRef.count }} <!-- 需确保 objRef 是 reactive 或解包后的 ref -->
</template>
4.如何监听ref或reactive的变化?
- 使用watch或watchEffect:
js 体验AI代码助手 代码解读复制代码watch(countRef, (newVal) => { /* ... */ });
watch(() => state.count, (newVal) => { /* ... */ });
5.ref和reactive的性能差异
- 基本类型:
ref
更高效(无需Proxy
代理)。 - 对象类型:性能差异可忽略。
七、watch、watchEffect、computed的区别
核心区别
特性 | computed | watch | watchEffect |
---|---|---|---|
用途 | 派生响应式数据 | 监听数据变化,执行副作用操作 | 自动收集依赖,执行副作用操作 |
返回值 | 只读的 Ref 对象 | 返回停止监听的函数 | 返回停止监听的函数 |
依赖收集 | 自动收集依赖,惰性计算(缓存结果) | 需显式指定监听源 | 自动收集依赖,立即执行 |
新旧值获取 | 无 | 可获取旧值和新值 | 无旧值,只跟踪最新值 |
执行时机 | 依赖变化时重新计算 | 默认在组件更新前执行(flush: 'pre' ) | 默认在组件更新前执行(类似 watch ) |
异步处理 | 不支持 | 支持异步操作 | 支持异步操作 |
核心使用场景
1.computed
- 场景:基于响应式数据生成新的值(如过滤列表、计算总和)
js
体验AI代码助手
代码解读
复制代码const fullName = computed(() => `${firstName.value} ${lastName.value}`);
2.watch
- 场景:监听特定数据变化,执行异步或复杂逻辑(如API请求、验证)
js 体验AI代码助手 代码解读复制代码watch(userId, async (newId, oldId) => {
const data = await fetchUser(newId);
userData.value = data;
}, { immediate: true }); // 立即执行一次
3.watchEffect
- 场景:自动跟踪依赖变化,执行副作用操作(如日志、DOM操作)
js 体验AI代码助手 代码解读复制代码watchEffect(() => {
console.log(`窗口大小:${window.innerWidth}x${window.innerHeight}`);
document.title = `Count: ${count.value}`;
});
高频面试题
1.三者核心区别是什么?
- computed:派生数据,有缓存,惰性计算
- watch:显式监听数据源,支持新旧值对比和异步
- watchEffect:自动收集依赖,立即执行,无旧值
2.watch和watchEffect的依赖收集方式有何不同?
- watch:需显式指定监听目标(如
() => state.a
) - watchEffect:自动收集回调函数内使用的所有响应式依赖
3.如何停止watch或watchEffect的监听?
- 调用它们返回的停止函数
js 体验AI代码助手 代码解读复制代码const stop = watch(data, callback);
stop(); // 停止监听
4.computed和普通函数的区别
- computed会缓存结果,依赖不变时不重新计算;普通函数每次调用时都会执行
5.watch的immediate和deep选项的作用
immediate:true
:立即执行回调(初始值触发)deep:true
:深度监听对象/数组内部变化
6.什么情况下使用watchEffect替代watch?
- 当依赖项不明确或需要自动跟踪多个依赖时(如同时监听多个状态)
底层原理与性能优化
1.computed缓存机制
- 内部通过
dirty
标志位标记是否需要重新计算,依赖未变化时直接返回缓存值
2.watch的异步调度
- 默认在组件更新前执行(
flush:'pre'
),可配置'post'
(组件更新后)或'sync'
(同步执行)
3.watchEffect的依赖收集
- 在首次执行回调时收集所有响应式依赖(类似Vue2的watcher依赖收集)
4.性能注意事项
- 避免过度使用watchEffect:自动依赖收集可能导致不必要的重复执行
- 合理使用computed缓存:减少重复计算开销
- 及时清理副作用:在
onUnmounted
中停止监听,避免内存泄漏。
代码实例对比
1.computed VS watch
js 体验AI代码助手 代码解读复制代码// 计算总价(推荐用 computed)
const total = computed(() => items.value.reduce((sum, item) => sum + item.price, 0));
// 监听总价变化(watch 不适用于此类场景)
watch(total, (newVal) => {
console.log("总价变化:", newVal); // 此时 computed 更高效
});
2.watch VS watchEffect
js 体验AI代码助手 代码解读复制代码// watch:显式监听多个依赖
watch([a, b], ([newA, newB], [oldA, oldB]) => {
console.log(`a从${oldA}变为${newA}, b从${oldB}变为${newB}`);
});
// watchEffect:自动收集依赖
watchEffect(() => {
console.log(`a=${a.value}, b=${b.value}`); // 自动跟踪 a 和 b
});
Vue3新增特性
1.watch支持监听多个数据源
js
体验AI代码助手
代码解读
复制代码watch([ref1, () => reactiveObj.prop], ([val1, val2]) => { /* ... */ });
2.watchPostEffect和watchSyncEffect
- watchPostEffect:等同于
watchEffect(..., { flush: 'post' })
,在DOM更新后执行。 - watchSyncEffect:等同于
watchEffect(..., { flush: 'sync' })
,同步执行
总结
- computed:用于派生数据,优先使用
- watch:用于监听特定变化数据,需精确控制依赖
- watchEffect:用于自动依赖跟踪,简化副作用管理