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

显示模式

登录
ARCHIVE DOCUMENTVUE

Vue 3 面试题八股集合(2023 原文整理版)

所属馆藏
Vue
文件格式
Markdown
原始路径
Vue/35-vue3面试题八股集合——2023
本文目录19 个章节
  1. 一、Vue 3 比 Vue 2 有什么优势?
  2. 二、描述 Vue 3 生命周期
  3. 三、如何看待 Composition API 和 Options API?
  4. 四、Vue 3 更新了哪些重要功能?
  5. 五、Proxy 和 Object.defineProperty 的区别
  6. 六、Vue 3 为什么通常比 Vue 2 更高效?
  7. 七、Vue 3 如何实现响应式?
  8. 八、Vue 3 编译器做了哪些优化?
  9. 九、watch 和 watchEffect 的区别
  10. 十、Teleport
  11. 十一、reactive、ref、toRef 和 toRefs
  12. 十二、谈谈 Pinia
  13. 十三、EventBus 与 mitt
  14. 十四、<script setup> 是什么?
  15. 十五、v-if 和 v-for 的优先级哪个高?
  16. 十六、setup 中如何获得组件实例?
  17. 十七、总结
  18. 官方参考
  19. 原文出处

Vue 3 面试题八股集合(2023 原文整理版)

原文发布于 2023 年,下面保留原有问题清单和知识主线,并按 Vue 3.5、Pinia、Vue Router 4 及当前官方文档修正错误。所谓“八股”只能帮助建立索引,具体项目仍应以目标版本的 API 和源码为准。

一、Vue 3 比 Vue 2 有什么优势?

Vue 3 的主要变化包括:

  • 使用基于 Proxy 的响应式系统,能够拦截对象新增、删除、数组索引和集合操作;
  • Composition API 提供了更适合逻辑复用和复杂业务组织的方式;
  • 更好的 TypeScript 类型声明和泛型推导;
  • 编译器增加静态提升、Patch Flags、Block Tree 等优化,减少无意义的运行时 diff;
  • 核心 API 更模块化,支持更好的 tree-shaking;
  • 提供 Fragment、Teleport、Suspense、<script setup>、多参数 v-model 等能力;
  • Vue 3 仍然支持 Options API,不要求所有项目迁移到 Composition API。

“性能更好、体积更小”不是所有场景下的绝对保证。实际结果取决于模板、组件规模、构建方式、第三方依赖和业务代码。Vue 2.7 也提供了部分 Composition API 兼容能力,但其核心响应式仍是 Vue 2 的实现,不能把 Vue 3 的所有行为直接套到 Vue 2.7。

二、描述 Vue 3 生命周期

Options API

Vue 3 Options API 的常用生命周期是:

  1. beforeCreate:组件实例初始化后调用,此时 data、computed、watch 等选项还没有全部完成初始化。
  2. created:响应式 data、computed、methods、watch 等已经准备好,但 DOM 尚未挂载。
  3. beforeMount:首次 render/patch 之前调用。
  4. mounted:组件 DOM 已挂载。只有客户端才有真实 DOM,SSR 期间不会执行。
  5. beforeUpdate:响应式状态变化、重新渲染和 patch 之前调用。
  6. updated:组件 DOM 更新后调用。若需要等待本轮 DOM 更新完成,也可以使用 nextTick()
  7. beforeUnmount:组件卸载前调用,适合移除监听器、停止定时器和取消资源。
  8. unmounted:组件及其子树卸载后调用。
  9. activated / deactivated:被 <KeepAlive> 缓存的组件激活或停用时调用。
  10. errorCaptured:捕获后代组件错误时调用,可以返回 false 阻止继续向上传播。
  11. renderTracked / renderTriggered:开发调试渲染依赖时使用,不应作为生产业务逻辑的主要依据。
  12. serverPrefetch:SSR 服务端预取数据时使用。

原文生命周期配图(本地化)

Composition API

Composition API 没有 beforeCreatecreated 的同名 hook,setup() 承担组件创建阶段的组合式逻辑。其他生命周期通过 onXxx 注册:

<script setup>
import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onActivated,
  onDeactivated,
} from 'vue'

onMounted(() => {
  console.log('DOM 已挂载')
})

onUnmounted(() => {
  console.log('清理资源')
})
</script>

Composition API 生命周期必须在 setup() 同步执行期间注册:

// ✅ 注册发生在 setup 的同步阶段
onMounted(() => {})

// ❌ 不要在 setTimeout 或 await 之后才注册
setTimeout(() => onMounted(() => {}), 0)

onMountedonUpdatedonUnmounted 等客户端生命周期不会在 SSR 期间执行;需要服务端数据预取时使用 onServerPrefetch 或框架提供的数据获取方案。

三、如何看待 Composition API 和 Options API?

两者都是 Vue 3 的正式 API,不存在“Composition API 完全取代 Options API”的要求。

Options API

export default {
  props: {
    title: String,
  },
  data() {
    return {
      count: 0,
    }
  },
  computed: {
    doubled() {
      return this.count * 2
    },
  },
  methods: {
    increment() {
      this.count++
    },
  },
}

Options API 按 datamethodscomputedwatch、生命周期等选项组织代码,适合小型组件、团队已有 Vue 2 经验或更偏好固定结构的项目。

Composition API

<script setup>
import { computed, ref } from 'vue'

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

function increment() {
  count.value++
}
</script>

Composition API 可以把同一业务逻辑的状态、计算、请求、清理放在一起,也能抽取为 composable。大型组件、复杂交互和 TypeScript 项目通常更容易从中受益。它的优势是组织和复用方式不同,不代表自动让每一段代码都更快。

原文 Composition API 配图(本地化)

四、Vue 3 更新了哪些重要功能?

1. createApp

Vue 2 常见写法是 new Vue({ ... });Vue 3 使用应用实例:

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

应用级插件、组件和全局属性通过 app 注册,多个应用实例之间不会共享同一个全局配置。

2. 新的响应式系统和 Composition API

Vue 3 使用 Proxy 代理对象,并提供 refreactivecomputedwatch 和 composables。

3. Fragment

组件模板可以有多个根节点,不必为了满足单根限制额外包一层无意义的元素。多根组件的 $attrs 继承需要显式处理。

4. Teleport

可以把组件的 DOM 渲染到当前组件逻辑层级之外,常用于模态框、通知和全局浮层。

5. Suspense

用于协调异步依赖的加载状态。它仍属于需要根据目标版本和框架 SSR 方案谨慎使用的能力,不应把它当成任意 Promise 的通用错误处理器。

6. emits 和多参数 v-model

<script setup>
const emit = defineEmits(['change'])
const model = defineModel('title') // Vue 3.4+

function update() {
  emit('change')
  model.value = 'new title'
}
</script>

Vue 3 用 v-model:titleupdate:title 替代了很多 Vue 2 .sync 的用法。defineModel 是 Vue 3.4+ 的编译器宏;在更早版本可以显式声明 prop 和 update 事件。

7. <script setup> 和 tree-shaking

Vue 3 的核心包采用更模块化的 API 设计,构建工具可以移除没有使用的部分。Vite 是当前 Vue 官方生态常用的构建工具;Vue CLI 仍可维护旧项目,但已处于维护模式,不应把“改进的 Vue CLI”作为 Vue 3 的主要新功能。

Vue 3 还移除了过滤器、组件实例 $on/$off/$once、部分 Vue 2 keyCode 修饰符等旧 API,但过渡动画、异步组件等并没有被整体移除。

五、Proxy 和 Object.defineProperty 的区别

Proxy 和 Object.defineProperty 都可以参与实现响应式,但能力和边界不同:

对比项ProxyObject.defineProperty
代理范围代理整个对象,操作通过 trap 拦截需要逐个定义已有属性的 getter/setter
新增/删除属性可以拦截 setdeletePropertyVue 2 需要 $set / $delete 等额外方案
数组索引可以拦截索引和 length 相关操作Vue 2 对数组索引和 length 有限制
集合类型Vue 3 对 Map/Set 提供专门 handler不能直接获得同样的集合操作语义
浏览器兼容依赖 Proxy,不能支持 IE 11ES5 环境兼容性更广
深度转换Vue 3 通常在读取嵌套对象时惰性代理Vue 2 会递归观测可观测对象属性

Proxy 本身不会自动让嵌套对象深度响应式;这是 Vue 的 reactive() 对读取结果继续转换的结果。性能也不能简单概括为“Proxy 永远更快”,应结合具体数据规模和更新模式评估。

六、Vue 3 为什么通常比 Vue 2 更高效?

Vue 3 的性能改进主要来自多个层面:

  1. Proxy 响应式减少了 Vue 2 新增属性、数组索引等特殊处理;
  2. 编译器识别静态节点,进行静态提升;
  3. Patch Flags 告诉运行时节点哪些部分可能变化;
  4. Block Tree 记录动态子节点,减少无关节点遍历;
  5. 事件处理器和部分常量可以被缓存;
  6. 核心 API 模块化,有利于 tree-shaking;
  7. 运行时、编译器和 SSR 分别针对不同场景优化。

原文提到 Vue 3 使用类似 React 的 Fiber 算法,这是错误的。Vue 3 使用自己的 renderer 和 Scheduler,并没有把 React Fiber 作为 Vue 的 diff 实现。Vue 2 的双端 diff 和 Vue 3 的 keyed diff 都会比较新旧子节点;Vue 3 的优势来自编译提示和运行时优化,而不是“只扫描新树”。

七、Vue 3 如何实现响应式?

简化模型如下:

读取 reactive 属性
  -> Proxy get
  -> track(target, key)
  -> 当前 effect 与 Dep 建立依赖

修改 reactive 属性
  -> Proxy set/delete
  -> trigger(target, key)
  -> 通知 effect、computed、watch 或组件 render effect
import { effect, reactive } from 'vue'

const state = reactive({ count: 0 })

effect(() => {
  console.log(state.count)
})

state.count++

Vue 3.5 的内部实现已经使用 DepLink 和版本号维护依赖关系,不能把旧版 dep.wdep.n 代码直接称作当前实现。应用开发只需要使用公开的 reactiverefcomputedwatch 等 API。

八、Vue 3 编译器做了哪些优化?

1. 静态提升(hoist static)

不会变化的 VNode 可以被提升到 render 函数外部,避免每次 render 重复创建。

2. Patch Flags

编译器为动态节点标记可能变化的部分,例如文本、class、style、props、事件等,运行时可以针对性 patch。

3. Block Tree / dynamicChildren

编译器把动态节点收集到 block 中,运行时更新时重点处理动态子节点,不必对整棵静态树做同等强度的比较。

4. 事件处理器缓存

在满足条件时,编译器可以缓存稳定的事件处理器,减少每次 render 创建新函数的成本。具体是否启用也受构建配置和表达式影响。

5. SSR 优化

Vue 3 的 SSR 编译路径可以将适合服务端输出的内容生成更高效的字符串,不能把客户端 VNode 优化和 SSR 字符串渲染混为一谈。

九、watchwatchEffect 的区别

  • watch 需要显式指定 source 和 callback,默认惰性执行;
  • watchEffect 会立即运行,并自动追踪同步执行期间读取的响应式依赖;
  • watch callback 可以拿到 new value 和 old value;
  • watchEffect 更关注“执行副作用”,没有 old value 参数;
  • watch 可以使用 immediatedeepflushonce 等选项;
  • watchEffect 在第一个 await 之前读取的依赖会被追踪,await 之后的读取不会自动成为本次 effect 的依赖;
  • 两者都应该清理过期请求、订阅和定时器。
<script setup>
import {
  onWatcherCleanup,
  reactive,
  ref,
  watch,
  watchEffect,
} from 'vue'

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

// ref:默认不立即执行,修改后回调
watch(
  sum,
  (newValue, oldValue) => {
    console.log('sum 变化了', newValue, oldValue)
  },
  { immediate: true },
)

// 多个 source
watch(
  [sum, msg],
  ([newSum, newMsg], oldValues) => {
    console.log(newSum, newMsg, oldValues)
  },
  { immediate: true },
)

// 直接 watch reactive 对象时默认深度追踪
watch(person, (newValue, oldValue) => {
  // 深层属性原地修改时,newValue 和 oldValue 可能是同一个对象引用
  console.log(newValue, oldValue)
})

// watch 某个属性必须传 getter
watch(() => person.name, (newValue, oldValue) => {
  console.log(newValue, oldValue)
})

// Vue 3.5+ 支持数字 deep,限制遍历深度
watch(
  () => person.job,
  (newJob, oldJob) => {
    console.log(newJob, oldJob)
  },
  { deep: 1 },
)

watchEffect(() => {
  console.log('自动追踪', sum.value, msg.value)
})

watch(sum, value => {
  const controller = new AbortController()

  void fetch(`/api/sum/${value}`, { signal: controller.signal })
    .catch(error => {
      if (error?.name !== 'AbortError') console.error(error)
    })
  onWatcherCleanup(() => controller.abort())
})
</script>

直接 watch reactive 对象时,深度遍历和性能成本需要注意;如果只关心一个字段,应传 getter。watch 的默认 flush 是 pre,还可以选择 postsync;读取更新后的 DOM 时通常使用 flush: 'post'nextTick()

原文 watch 配图(本地化)

十、Teleport

Teleport 可以把组件模板的一部分 DOM 挂载到当前组件之外,同时保留逻辑上的组件父子关系:

<template>
  <button @click="visible = true">打开</button>

  <Teleport to="#modals">
    <div v-if="visible" class="modal">
      <button @click="visible = false">关闭</button>
    </div>
  </Teleport>
</template>

<script setup>
import { ref } from 'vue'

const visible = ref(false)
</script>

页面需要有目标节点:

<div id="app"></div>
<div id="modals"></div>

to 通常是 CSS 选择器字符串,也可以绑定已经存在的 DOM 元素;它不是 Vue 组件实例。原文“可以直接把 Vue 组件实例作为 to”是不准确的。

<script setup>
import { ref } from 'vue'

const target = ref(null)
</script>

<template>
  <div ref="target"></div>
  <Teleport :to="target">
    <div>挂载到 target 元素</div>
  </Teleport>
</template>

Vue 3.5+ 还提供 defer,用于在同一挂载批次内稍后解析目标;目标最终仍必须存在:

<Teleport defer to="#late-target">
  <div>延迟解析目标</div>
</Teleport>

Teleport 移动的是 DOM 位置,不会改变组件的逻辑父级,因此 props、emits、provide/inject 和事件传播仍按组件树处理。样式则要额外考虑 CSS 选择器、层叠上下文和全局样式。

十一、reactivereftoReftoRefs

ref

ref 可以包装原始值或对象:

const count = ref(0)
count.value++

const user = ref({ name: 'Vue' })
user.value.name = 'Vue 3'

模板中会自动解包顶层 ref;普通 JavaScript 代码中需要使用 .value。对象 ref 的 value 会被 Vue 转换为响应式对象,但替换整个 value 仍然是 ref 自身的更新。

reactive

reactive 接收对象、数组、Map、Set 等可代理值,返回 Proxy:

const state = reactive({ count: 0 })
state.count++

它不能直接接收 null、字符串等原始值。不要随意解构 reactive 对象,否则普通解构值会失去与原对象属性的连接。

toRef

toRef 为响应式对象的某个属性创建与原对象保持同步的 ref:

const state = reactive({ age: 18 })
const age = toRef(state, 'age')

age.value++
console.log(state.age) // 19

toRef 也可以用于可能暂时不存在的属性;与 toRefs 相比,它更适合只取一个字段。Vue 3.3+ 还支持把值、ref 或 getter 规范化为 ref 的签名。

原文 toRef 配图(本地化)

toRefs

toRefs 把一个响应式对象转换为普通对象,其中每个已有属性都是与原对象同步的 ref:

const state = reactive({ name: 'Vue', version: 3 })
const { name, version } = toRefs(state)

它只会转换调用时可枚举的已有属性。后续新增属性不会自动出现在已经返回的普通对象中,这种情况可以单独使用 toRef(state, 'newKey')

原文 toRefs 配图(本地化)

十二、谈谈 Pinia

Pinia 是 Vue 官方生态当前推荐的状态管理库。它提供多个相互独立的 store,支持 state、getters、actions、插件、SSR 和 DevTools,并且不强制使用 mutations。

import { defineStore } from 'pinia'

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

版本边界需要特别说明:

  • Pinia 2 支持 Vue 2.7/ Vue 3,旧项目应根据 Pinia 2 文档确认兼容版本;
  • 当前 Pinia 3 面向 Vue 3,Pinia 官方文档已说明 Vue 2 支持被移除;
  • Vuex 3/4 仍适合维护已有项目,但新 Vue 3 项目通常优先考虑 Pinia;
  • “Pinia 压缩后一定是 1.6KB”等数字会随版本和构建方式变化,不应当作固定指标。

十三、EventBus 与 mitt

Vue 2 的组件实例有 $on$emit$off$once,可以通过一个 Vue 实例实现 EventBus:

// Vue 2 历史写法
const bus = new Vue()
bus.$emit('change', payload)
bus.$on('change', handler)
bus.$off('change', handler)

Vue 3 移除了组件实例上的 $on$off$once,因此旧的 Vue 实例 EventBus 不能直接迁移。优先考虑 props/emits、provide/inject、Pinia;确实需要跨树事件时可使用 mitt 等独立事件库:

import mitt from 'mitt'

type Events = {
  change: { id: number }
}

export const emitter = mitt<Events>()

function handleChange(event: Events['change']) {
  console.log(event.id)
}

emitter.on('change', handleChange)
emitter.emit('change', { id: 1 })
emitter.off('change', handleChange)

mitt 不是 Vue 内置库,也不负责响应式状态、持久化、请求缓存或组件生命周期。监听器必须在合适的生命周期中移除,否则会造成重复响应和内存泄漏。

十四、<script setup> 是什么?

<script setup> 是 Vue 单文件组件中编译时语法。顶层变量、函数和导入的组件可以直接在模板中使用:

<script setup>
import { ref } from 'vue'
import ChildPanel from './ChildPanel.vue'

const count = ref(0)
</script>

<template>
  <ChildPanel :count="count" />
  <button @click="count++">{{ count }}</button>
</template>

主要特点:

  • 顶层绑定无需手动 return;
  • 导入的组件自动注册;
  • definePropsdefineEmitsdefineExposedefineOptionsdefineModel 等是编译器宏,不需要从 Vue 导入;
  • 默认不会把所有内部变量暴露给父组件;需要通过 defineExpose 明确公开;
  • useAttrsuseSlots 用于访问 attrs 与 slots;
  • 它减少了组件样板代码,但不是“任何场景运行性能都更好”的保证。

十五、v-ifv-for 的优先级哪个高?

  • Vue 2:同一元素上 v-for 优先级高于 v-if
  • Vue 3:同一元素上 v-if 优先级高于 v-for,因此 v-if 不能使用循环别名;
  • 两个版本都不推荐在同一元素上混用;应使用计算属性、外层 v-if<template v-for>
<!-- Vue 3 推荐:先过滤,再循环 -->
<li v-for="item in visibleItems" :key="item.id">
  {{ item.name }}
</li>

十六、setup 中如何获得组件实例?

getCurrentInstance() 是 Vue 的低层内部 API,可以在 setup 同步执行期间获取当前组件内部实例:

<script setup>
import { getCurrentInstance } from 'vue'

const instance = getCurrentInstance()

if (instance) {
  console.log(instance.uid)
}
</script>

它可能返回 null,不应在异步回调、普通工具函数或业务模块中随意调用。内部实例字段不是稳定的公共业务 API,升级 Vue 时可能变化。

通常应优先使用公开 API:

  • 访问 props:defineProps
  • 触发事件:defineEmits
  • 访问 attrs/slots:useAttrsuseSlots
  • 暴露子组件方法:defineExpose + 模板 ref;
  • 跨层级依赖:provide / inject

十七、总结

面试时可以用以下主线回答:

  1. Vue 3 通过 Proxy 响应式、编译时优化和模块化 API 改善 Vue 2 的限制;
  2. Composition API 解决的是逻辑组织和复用问题,Options API 仍然可用;
  3. Vue 3 的响应式核心是 Proxy 的 track/trigger,编译器通过静态提升、Patch Flags 和 Block Tree 减少运行时工作;
  4. watch 显式指定 source,watchEffect 自动追踪副作用,深度监听、flush 和 cleanup 要结合场景说明;
  5. Teleport 改变 DOM 位置但不改变逻辑组件关系;
  6. refreactivetoReftoRefs 的主要区别在于包装方式和解构后的引用关系;
  7. Vue 3 新项目优先考虑 Pinia,Vuex、EventBus 和旧实例 API 要明确版本边界;
  8. getCurrentInstance() 是低层 API,不能作为日常组件通信方案。

官方参考

原文出处

作者:小小打工媛

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

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

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

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

按分类浏览

10 COLLECTIONS