Skip to content

vue3面试题

约 5596 字大约 19 分钟

2025-06-01

  • vue2vue3.0vue3.2备注
    beforeCreatesetup组件创建之前 可以获取顶级实例对象
    createdsetup组件创建完成,可以获取变量
    beforeMountonBeforeMount挂载前,VNdom创建完成,真实dom未渲染
    mountedonMounted挂载完成,真实dom创建完成,可以获取dom
    beforeUpdateonBeforeUpdatedom更新前触发
    updatedonUpdateddom更新完成触发
    beforedestroy,destroyedbeforeUnmountonBeforeUnmount组件卸载后触发 所有的挂载的数据 子组件全部卸载后触发
    errorCapturedonErrorCaptured在捕获一个来自后代组件的错误时被调用
    renderTrackedonRenderTracked跟踪虚拟 DOM 重新渲染时调用
    renderTriggeredonRenderTriggered当虚拟 DOM 重新渲染被触发时调用
    activatedactivatedonActivated缓存组件激活时调用
    deactivateddeactivatedonDeactivated缓存组件失活时调用

高频面试题

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):
    • 逻辑分散在 datamethodscomputed 等选项中,复杂组件易导致代码碎片化

Vue 3(Composition API)

  • 引入 setup() 函数,通过 refreactive 等函数按功能组织代码,提升逻辑复用性和可维护性

✨ 4. 新特性

  • 多根节点支持(Fragments):
    • Vue 3 允许多个根元素,无需外层包裹 <div>

v-model 改进

  • 支持多个 v-model 绑定(如 v-model:titlev-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属性的propevent属性来进行自定义。原生的v-model,会根据标签的不同生成不同的事件和属性 。

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 的区别

  1. v-show
    1. 作用: 控制元素显示隐藏
    2. 语法: v-show = "表达式" 表达式值为 true 显示, false 隐藏
    3. 原理: 切换 display:none 控制显示隐藏
    4. 场景:频繁切换显示隐藏的场景
  2. v-if
    1. 作用: 控制元素显示隐藏(条件渲染)
    2. 语法: v-if= "表达式" 表达式值 true显示, false 隐藏
    3. 原理: 基于条件判断,是否创建 或 移除元素节点
    4. 场景: 要么显示,要么隐藏,不频繁切换的场景

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、并且首屏加载速度更快等优点。不过它也有一些缺点,比如我们的开发条件会受到限制,服务器端渲染只支持beforeCreatecreated两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于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 中的数据都会增加 gettersetter,会收集对应的 watcher
  • v-ifv-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 元素
  • 如果用在子组件上,引用就指向组件实例

所以常见的使用场景有:

  1. 基本用法,本页面获取 DOM 元素
  2. 获取子组件中的 data
  3. 调用子组件中的方法

17、ref和reactive的区别

核心区别与使用场景

特性refreactive
适用数据类型基本类型(string/number/boolean)或对象对象或数组
访问方式通过 .value 访问直接访问属性
响应式原理内部对对象类型调用 reactive基于 Proxy 的深层代理
模板自动解包在模板中无需 .value直接使用属性
解构响应性需用 toRefs 保持响应性直接解构会丢失响应性,需用 toRefs

核心面试题

1.为什么ref需要.value?

  • 设计目的:统一处理基本类型和对象类型。
    • 基本类型无法通过Proxy代理,ref通过封装对象({ value:... })实现响应式
  • 底层实现
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的区别

核心区别

特性computedwatchwatchEffect
用途派生响应式数据监听数据变化,执行副作用操作自动收集依赖,执行副作用操作
返回值只读的 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:用于自动依赖跟踪,简化副作用管理