技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTVUE

Vue3.0面试题汇总

所属馆藏
Vue
文件格式
Markdown
原始路径
Vue/24-Vue3 0面试题汇总
本文目录16 个章节
  1. 一、Options API 与 Composition API 的区别?
  2. 二、Vue 3 性能提升主要体现在哪些方面?
  3. 三、Vue 3 为什么用 Proxy 替代 defineProperty?
  4. 四、Vue 3 响应式原理
  5. 五、说说 Vue 3 的 tree-shaking?
  6. 六、Vue 3 新特性有哪些?
  7. 七、组合式 API 生命周期钩子有变化吗?
  8. 八、watch 和 watchEffect 的区别?
  9. 九、v-if 和 v-for 哪个优先级高?
  10. 十、script setup 是做什么的?
  11. 十一、Vue 2 / Vue 3 组件通信方式?
  12. 十二、ref 与 reactive 的区别?
  13. 十三、EventBus 与 mitt 的区别?
  14. 十四、谈谈 Pinia?
  15. 原文出处
  16. 当前官方参考

Vue3.0面试题汇总

Composition API 是 Vue 3 的重要特性。Vue 2 项目中,大型组件的逻辑容易分散在 datacomputedmethodswatch 中,mixin 等复用方式也有命名冲突和来源不清晰的问题,TypeScript 推断能力有限。Composition API 提供了按逻辑关注点组织、提取和复用代码的另一种方式,但不是万能方案,Options API 在 Vue 3 中仍然受支持。

以下 14 题保留原文结构,并按当前官方文档修正。

一、Options API 与 Composition API 的区别?

  1. Options API 通过 datacomputedmethodswatch 等选项组织组件。它对小型和中型组件很直观;组件复杂后,同一功能可能分散在多个选项块。
  2. Composition API 把同一逻辑关注点的状态、计算属性、方法和副作用放在一起,可提取为 composable,使复杂功能更内聚。
  3. 两个主要收益:
    • 逻辑组织:修改一个功能时减少在多个选项块之间跳转。
    • 逻辑复用:通过函数参数和返回值明确数据来源,降低 mixin 的命名冲突和隐式合并问题。

Composition API 由普通函数和变量组成,通常有更自然的 TypeScript 推断,也减少了 this 指向问题。但它并不自动保证代码更清晰,也不自动消除命名或隐式依赖;设计不当的 composable 同样难维护。tree-shaking 依赖 ESM 与构建工具,而不是“所有 Composition API 函数都必然被摇掉”。

二、Vue 3 性能提升主要体现在哪些方面?

1. 编译阶段优化

  • patch flags 与 block tree:编译器标记已知动态节点和动态属性,运行时对这些位置走快速更新路径,而不是无差别遍历整棵子树。
  • 静态提升:可安全复用的静态 VNode 或静态属性会被提升或缓存,避免每次渲染重复创建。编译产物中的负数标记属于内部优化细节,不应当作“所有静态节点永远不 diff”的公共 API 保证。
  • handler caching:编译器在安全时缓存内联事件处理器,减少重复创建函数和不必要的监听器更新;它不是用户手工打开的运行时开关。
  • 静态字符串化与 SSR:客户端编译可把连续静态内容压缩成 static vnode 并以 HTML 挂载;SSR 通常编译为 HTML 字符串输出。两者目标相近,但产物和执行环境不同。

2. 源码与构建体积

Vue 3 提供 ESM 具名 API,支持 tree-shaking 的 bundler 可以移除未引用的可裁剪代码。最终体积取决于 Vue 版本、入口、依赖、生产配置和实际使用功能,不能保证固定“缩小一半”。

3. 响应式系统

Vue 2 递归转换初始化时已有属性,并包装七个数组变异方法;它不能自动检测对象属性直接新增/删除、数组索引和 length 的直接修改。Vue 3 的 reactive() 以 Proxy 为基础,可以拦截这些操作。原生 Proxy 只代理一层,Vue 通过访问嵌套对象时继续转换来形成深层响应式。

4. 实际优化原则

版本特性不是固定性能承诺。应先用 Vue DevTools、浏览器 Performance 面板测量,再按瓶颈采用稳定 props、代码分割、虚拟列表、v-oncev-memo 等手段。

三、Vue 3 为什么用 Proxy 替代 defineProperty?

Vue 2 的 Object.defineProperty() 需要逐个转换已有属性。它能递归观察已有嵌套对象,也能通过包装 pushpopshiftunshiftsplicesortreverse 响应数组变异;“数组 API 无法监听”是错误说法。它的边界主要是新增/删除对象键、索引赋值与修改 length

Proxy 面向目标对象的操作,可拦截 getsetdeletePropertyhasownKeys 等,因此 Vue 3 能覆盖上述边界,并支持对象、数组及 Map/Set 等集合类型。Proxy 返回新对象,使用时应持续操作代理。

这不意味着 Proxy 自动深度代理,也不意味着一定在所有场景更快;Vue 的深层转换、依赖收集和调度仍需要框架实现并应通过实际测量评价。

四、Vue 3 响应式原理

Vue 3 使用 Proxy、Reflect 和 effect 系统实现响应式。可把过程简化成三个教学阶段:

  1. reactive(raw) 创建或复用 raw 对象对应的代理。
  2. effect、computed、watch 或组件渲染读取 state.key 时,经 get 进入 track(target, key) 收集依赖。
  3. 写入 state.key 时,经 set 判断操作类型和值是否变化,再由 trigger(target, key) 调度相关依赖。
import { reactive, watchEffect } from 'vue'

const state = reactive({ count: 0, nested: { value: 1 } })

watchEffect(() => {
  console.log(state.count, state.nested.value)
})

state.count++
state.nested.value = 2

原生 Proxy 只代理 target 一层;Vue 在 Reflect.get() 得到嵌套对象时返回其响应式代理,于是形成深层转换。数组还涉及 ADDSETDELETElength 与迭代依赖,不能用“只要是自有属性或新旧值相同就 trigger”概括。Composition API 中通常写 state.keyref.value,不是通用的 this[key]

五、说说 Vue 3 的 tree-shaking?

Tree-shaking 是 dead-code elimination 的一种构建优化。Vue 3 以 ESM 具名导出提供许多 API:

import { computed, ref } from 'vue'

const count = ref(0)
const double = computed(() => count.value * 2)

支持 tree-shaking 的 bundler 静态分析 ESM 依赖,再删除可证明未使用且无副作用的代码。Vue 提供可裁剪的模块形态,但真正执行分析和删除的是构建工具。

Vue 2 的许多全局 API 集中在单例对象上,较难按属性调用裁剪;也不能绝对说 Vue 2 的任何未用功能都会进入所有生产包。tree-shaking 可能降低下载、解析和执行成本,收益取决于构建配置和代码,应检查实际产物而非承诺固定比例。

六、Vue 3 新特性有哪些?

  1. 性能与编译优化:Proxy 响应式、patch flags、block tree、静态提升、handler caching 等。
  2. 更好的 TypeScript 支持:Vue 3 源码和公共类型以 TypeScript 构建,复杂组件与工具链推断通常更好。
  3. Composition APIsetup()refreactivecomputedwatch 等可按逻辑组合,但 Options API 仍可使用。
  4. 新内置能力
    • Fragment:组件支持多个根节点。
    • Teleport:把内容渲染到组件 DOM 层级之外的目标节点。
    • Suspense:协调异步依赖与 fallback,具体稳定性和适用边界以当前文档为准。
  5. tree-shaking 友好的 ESM API:效果由 bundler 和实际引用决定。
  6. Custom Renderer API:可构建非 DOM 渲染器,例如 Canvas、WebGL 或原生平台渲染;它不是“用 DOM 实现 WebGL”。

生命周期顺序应为:setup() → beforeCreate → created → beforeMount → mountedsetup() 在所有 Options API 生命周期之前执行,且其中没有组件 this

七、组合式 API 生命周期钩子有变化吗?

setup() 取代了在组合式逻辑中使用 beforeCreatecreated 的需求。其他常见对应关系为:

  • beforeMountonBeforeMount
  • mountedonMounted
  • beforeUpdateonBeforeUpdate
  • updatedonUpdated
  • beforeUnmountonBeforeUnmount
  • unmountedonUnmounted
  • errorCapturedonErrorCaptured

这些钩子必须在 setup() / <script setup> 的同步调用栈中注册,以绑定当前组件实例。setup()this;若同时使用 Options API,setup 返回的绑定可在后续 Options hooks 中通过实例访问。

Options API 与 Composition API 生命周期对照

八、watch 和 watchEffect 的区别?

  • watch 显式指定一个或多个数据源,默认惰性执行,可拿到新旧值并配置 immediatedeep 等。
  • watchEffect 立即运行,自动收集同步执行期间读取的依赖,适合副作用;它没有 new/old 参数,返回值也不作为派生状态。
  • computed 返回缓存的派生 ref;watchEffect 用于执行副作用。
  • 异步 watchEffect 只跟踪第一个 await 之前同步读取的依赖。
import { reactive, ref, watch, watchEffect } from 'vue'

const sum = ref(0)
const msg = ref('你好啊')
const person = reactive({
  name: '张三',
  age: 18,
  job: { j1: { salary: 20 } }
})

watch(sum, (newValue, oldValue) => {
  console.log(newValue, oldValue)
}, { immediate: true })

watch([sum, msg], ([newSum, newMsg], [oldSum, oldMsg]) => {
  console.log(newSum, newMsg, oldSum, oldMsg)
}, { immediate: true })

// 直接 watch reactive 对象时隐式 deep
watch(person, (newValue, oldValue) => {
  // 深层就地修改时 newValue === oldValue,因为两者指向同一代理
  console.log(newValue, oldValue)
}, { immediate: true })

watch(() => person.name, (newValue, oldValue) => {
  console.log(newValue, oldValue)
})

watch(
  [() => person.name, () => person.age],
  (newValues, oldValues) => console.log(newValues, oldValues)
)

watch(() => person.job, (newValue, oldValue) => {
  console.log(newValue, oldValue)
}, { deep: true })

watchEffect(() => {
  console.log(sum.value, person.name)
})

watchEffect(async () => {
  console.log(sum.value) // 会被跟踪
  await Promise.resolve()
  console.log(msg.value) // 此次 await 后读取不会被这次运行自动跟踪
})

直接 watch reactive 对象时会隐式进行深层监听。Vue 3.5+ 可用 { deep: false } 将遍历限制在根层;如果要兼容更早版本或只监听明确字段,建议改用 getter。无论哪种方式,深层就地修改都不是快照机制。

watch 与 watchEffect 使用方式对照

九、v-if 和 v-for 哪个优先级高?

Vue 2 同一元素上 v-for 优先;Vue 3 改为 v-if 优先。因此 Vue 3 中同节点的 v-if="!todo.done" 无法读取尚未进入作用域的 todo

优先使用计算属性过滤:

const visibleTodos = computed(() => todos.value.filter(todo => !todo.done))

也可把 v-for 放到 <template>

<template v-for="todo in todos" :key="todo.id">
  <li v-if="!todo.done">{{ todo.text }}</li>
</template>

十、script setup 是做什么的?

<script setup> 是单文件组件(SFC)的编译期语法糖,顶层绑定可直接用于模板,导入组件也可直接使用,不需要 components 注册。

<script setup lang="ts">
import { useAttrs, useSlots } from 'vue'

const props = defineProps<{ title: string }>()
const emit = defineEmits<{ save: [id: number] }>()
const attrs = useAttrs()
const slots = useSlots()

defineExpose({ save: () => emit('save', 1) })
</script>

definePropsdefineEmitsdefineExpose 是编译器宏,无需导入;useAttrsuseSlots 是运行时函数,需要从 vue 导入。组件默认不会通过模板 ref 暴露所有顶层绑定,可用 defineExpose 明确公开。

十一、Vue 2 / Vue 3 组件通信方式?

Vue 3 常见方式:

  • props / emits
  • v-modelupdate:modelValue,或具名 v-model:prop
  • template ref + defineExpose
  • $attrs
  • slots
  • provide / inject
  • Pinia
  • 必要时使用 mitt 等外部事件发射器

provide/inject 沿祖先组件链按 key 查找最近 provider,不是原型链;提供 ref 时注入到脚本中仍是 ref。

Vue 2 历史方式还包括 .sync$listeners$children、实例 EventBus 等。迁移到 Vue 3 时:.sync 改为具名 v-model$listeners 合并进 $attrs,不应依赖私有组件层级 API。Vue 3 移除的是实例 $on$off$once,不是组件的声明式 $emit

十二、ref 与 reactive 的区别?

  • ref() 可接收 primitive 或对象,JavaScript 中通过 .value 读写;模板顶层 ref 通常自动解包。
  • reactive() 只能接收对象类型(对象、数组、Map、Set 等),返回深层响应式 Proxy。
  • ref(primitive) 通过自身 getter/setter 跟踪 .value;只有 ref 持有对象时,内部对象才会通过 reactive() 深层转换。因此不能简单说“ref 底层都是 reactive”。
  • reactive 根对象不宜整体替换,直接解构其 primitive 属性会断开响应式连接;ref 的 .value 可以整体替换。
  • 数组和集合中的 ref 还有不自动解包等边界。

两者能力有交集但并非“ref 能做 reactive 的一切”。可优先使用 ref 声明状态,再根据对象结构、整体替换和解构需求选择。

十三、EventBus 与 mitt 的区别?

Vue 2 常借助 Vue 实例的 $on/$emit/$off 实现 EventBus。Vue 3 移除了实例 $on/$off/$once,所以不能继续把 Vue 实例当事件总线;组件 $emit 仍然存在。

mitt 是与框架无关的事件发射器,提供 onemitoff 等 API,也支持 TypeScript 使用。它适合少量跨边界事件,但大量全局事件仍可能难以追踪;应优先考虑 props/emits、provide/inject 或 Pinia。库体积会随版本和构建方式变化,不应写死固定 bytes。

十四、谈谈 Pinia?

Pinia 是 Vue 官方推荐的状态管理库。Options Store 使用 defineStore() 定义 stategettersactions

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    double: state => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    }
  }
})

Pinia 没有 mutations,也支持以 Composition API 风格定义 Setup Store。每个 store 有唯一 id,调用 useCounterStore() 时创建或取得实例,通常不需要像 Vuex module 那样向根 store 手动注册;多个 store 可以显式组合。

它支持 TypeScript、Vue DevTools 和 SSR,但需理解边界:

  • 普通 SPA 刷新后内存状态消失是预期行为。
  • 持久化不是 Pinia 默认能力,可用插件或 $subscribe 自行实现,并评估敏感信息和 XSS 风险。
  • SSR 需要安全序列化服务端状态,并在客户端使用 store 前完成 hydration,避免跨请求状态污染。

不要把 Pinia 描述为固定压缩体积,也不要依据旧讨论断言“就是 Vuex 5 且绝不会再有其他版本”;以当前 Pinia 和 Vuex 官方文档为准。

原文出处

本文摘录、整理自:

作者:丝绒拿铁有点甜

原文链接:https://juejin.cn/post/7139921537896808479

来源:稀土掘金。著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

当前官方参考

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS