Vue 面试题(2026高频版):基础+进阶+手写+项目实战
结合最新面试趋势,整理了分梯度、带答题思路、附手写代码的Vue核心面试题,覆盖入门到资深级考点,帮你快速抓住重点、高效备考。
一、基础必懂(入门级,80%面试会问)
1. Vue 中 data 为什么是函数而不是对象?
核心答案:
- 组件是可复用的实例,若data是对象,所有组件实例会共享同一个data对象,导致数据互相污染;
- 若data是函数,每次创建组件实例时会执行函数,返回全新的data对象,保证每个实例数据独立。
补充:根实例(new Vue({ data: {} }))的data可以是对象,因为根实例只会创建一次,不存在复用问题。
2. 简述 Vue 的双向绑定原理(v-model 底层)
核心答案:
- v-model 是语法糖,本质是
v-bind:value + v-on:input; - 原生表单元素:
<input v-model="val">等价于<input :value="val" @input="val = $event.target.value">; - 自定义组件:父组件通过
v-model绑定,子组件接收modelValueprop,触发update:modelValue事件实现双向绑定。
手写示例(自定义组件v-model):
vue
<!-- 子组件 MyInput.vue -->
<template>
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
</template>
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<!-- 父组件使用 -->
<MyInput v-model="username" />
3. computed 和 watch 的核心区别及使用场景
| 维度 | computed | watch |
|---|---|---|
| 本质 | 计算属性,依赖其他数据返回新值 | 监听器,监听数据变化执行逻辑 |
| 缓存 | 有缓存(依赖不变则复用结果) | 无缓存,数据变就触发 |
| 同步/异步 | 仅支持同步 | 支持同步+异步(如请求接口) |
| 使用场景 | 数据推导(如拼接姓名、格式化金额) | 数据变化触发异步操作(如搜索防抖、联动请求) |
示例:
javascript
// computed:姓名拼接(同步、缓存)
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
// watch:搜索防抖(异步)
watch(searchKey, (newVal) => {
const timer = setTimeout(() => {
// 发起搜索请求
}, 300)
return () => clearTimeout(timer) // 清除防抖定时器
}, { immediate: true }) // 立即执行一次
4. v-if 和 v-show 的区别及选型依据
| 维度 | v-if | v-show |
|---|---|---|
| 渲染逻辑 | 条件假时移除DOM节点 | 条件假时隐藏(display:none) |
| 切换开销 | 高(销毁/重建组件) | 低(仅切换样式) |
| 初始渲染 | 条件假时不渲染 | 无论条件都渲染 |
| 适用场景 | 条件少变(如权限控制、页面Tab) | 条件频繁切换(如弹窗、下拉菜单) |
二、进阶核心(中级,区分度考点)
1. Vue2 和 Vue3 响应式原理的核心差异
| 维度 | Vue2 | Vue3 |
|---|---|---|
| 核心API | Object.defineProperty | Proxy + Reflect |
| 监听范围 | 仅监听对象属性(需遍历) | 监听整个对象 |
| 数组支持 | 重写数组方法(push/pop等) | 原生支持数组索引/长度变化 |
| 新增属性 | 需 Vue.set 手动监听 | 自动监听新增/删除属性 |
| 复杂类型 | 不支持Map/Set | 支持Map/Set/WeakMap等 |
Vue3响应式手写简化版:
javascript
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
// 依赖收集(简化版)
track(target, key)
return Reflect.get(target, key)
},
set(target, key, value) {
const result = Reflect.set(target, key, value)
// 派发更新(简化版)
trigger(target, key)
return result
}
})
}
2. 虚拟DOM(VNode)的核心作用及diff算法逻辑
虚拟DOM :用JS对象描述DOM结构(如 { tag: 'div', props: { id: 'box' }, children: [] }),避免直接操作真实DOM。
diff算法核心原则:
- 同层比较,不跨层级(如根节点div不会和子节点p比较);
- 列表对比依赖key:用key唯一标识节点,避免就地复用导致数据错乱;
- Vue3优化:PatchFlags标记静态节点,跳过无变化节点的对比,提升diff效率。
关键考点:v-for为什么必须加key?
- 不加key:Vue会就地复用节点(如列表排序后,仅更新内容不移动DOM),导致表单值错乱、动画异常;
- 禁止用index做key:列表顺序变化时index也会变,失去唯一标识作用,推荐用后端返回的唯一ID。
3. Vue Router 两种模式(hash/history)的区别及部署问题
| 维度 | hash模式 | history模式 |
|---|---|---|
| URL特征 | 带#(如/#/home) | 无#(如/home) |
| 底层原理 | 基于hashchange事件 | 基于HTML5 History API |
| 兼容性 | 兼容所有浏览器 | 需IE10+ |
| 部署要求 | 无需后端配置 | 需后端配置(刷新404) |
history模式部署解决(Nginx配置):
nginx
server {
location / {
try_files $uri $uri/ /index.html; # 所有请求转发到index.html
}
}
4. Pinia 对比 Vuex 的核心优势(Vue3主流)
| 维度 | Vuex | Pinia |
|---|---|---|
| 核心概念 | State/Mutation/Action/Module | State/Action/Getter(无Mutation) |
| 模块化 | 嵌套Module,命名空间复杂 | 每个Store独立,天然模块化 |
| TS支持 | 需手动定义类型 | 原生支持TS,类型推断完善 |
| 数据修改 | 必须通过Mutation(同步) | 直接修改State(同步/异步) |
| 体积 | 较大 | 更小,支持Tree-Shaking |
Pinia使用示例:
javascript
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({ name: '张三', age: 20 }),
getters: {
fullInfo: (state) => `${state.name}(${state.age}岁)`
},
actions: {
async updateName(newName) {
// 异步请求接口
await api.updateUser({ name: newName })
this.name = newName // 直接修改state
}
}
})
// 组件中使用
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
userStore.updateName('李四')
三、高级实战(资深级,项目经验考点)
1. Vue 项目性能优化的核心手段(分维度)
(1)代码层面
- 响应式优化:Vue3用
shallowReactive(浅响应)/markRaw(非响应式)减少监听开销; - 渲染优化:避免模板复杂表达式,用computed缓存结果;v-for和v-if不混用(v-if放外层);
- 事件优化:自定义事件及时解绑,滚动/输入事件加节流防抖。
(2)组件层面
- 异步组件:
defineAsyncComponent拆分大组件,按需加载; - 组件缓存:
keep-alive缓存频繁切换的组件(如Tab页),减少重复渲染; - 长列表优化:虚拟滚动(vue-virtual-scroller),只渲染可视区域。
异步组件示例:
vue
<script setup>
import { defineAsyncComponent } from 'vue'
// 按需加载弹窗组件
const Dialog = defineAsyncComponent(() => import('./Dialog.vue'))
</script>
(3)打包层面
- 路由懒加载:
const Home = () => import('./Home.vue'); - 第三方库按需引入:如Element Plus只引入需要的组件;
- CDN引入:Vue、Pinia等库通过CDN引入,减少打包体积。
2. Composition API 对比 Options API 的优势
| 维度 | Options API | Composition API |
|---|---|---|
| 逻辑组织 | 按选项分散(data/methods) | 按功能聚合(如登录逻辑放一起) |
| 逻辑复用 | mixins(命名冲突、来源不明) | Composables(组合函数,清晰复用) |
| TS支持 | 差,需额外配置 | 原生支持,类型推断完善 |
| 灵活性 | 低,受选项约束 | 高,按需组合逻辑 |
Composables复用示例(useRequest.js):
javascript
// composables/useRequest.js
import { ref } from 'vue'
export function useRequest(api) {
const data = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchData = async () => {
loading.value = true
try {
data.value = await api()
} catch (e) {
error.value = e
} finally {
loading.value = false
}
}
return { data, loading, error, fetchData }
}
// 组件中复用
import { useRequest } from '@/composables/useRequest'
const { data, loading, fetchData } = useRequest(api.getUserList)
fetchData()
3. Vue 项目中如何处理跨域问题
(1)开发环境(本地调试)
- 配置代理(Vite示例):
javascript
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000', // 后端接口地址
changeOrigin: true, // 开启跨域
rewrite: (path) => path.replace(/^\/api/, '') // 去掉/api前缀
}
}
}
}
(2)生产环境
- 后端配置CORS:设置
Access-Control-Allow-Origin为前端域名; - Nginx反向代理:将前端请求转发到后端,避免跨域。
四、手写题(高频压轴)
1. 实现 Vue 简易响应式(Vue3 思路)
javascript
// 依赖收集容器
const targetMap = new WeakMap()
let activeEffect = null
// 依赖收集
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) targetMap.set(target, (depsMap = new Map()))
let deps = depsMap.get(key)
if (!deps) depsMap.set(key, (deps = new Set()))
deps.add(activeEffect)
}
// 派发更新
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const deps = depsMap.get(key)
if (deps) deps.forEach(effect => effect())
}
// 响应式核心
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key)
return Reflect.get(target, key)
},
set(target, key, value) {
const result = Reflect.set(target, key, value)
trigger(target, key)
return result
}
})
}
// 副作用函数
function effect(fn) {
activeEffect = fn
fn() // 执行一次,触发依赖收集
activeEffect = null
}
// 测试
const state = reactive({ count: 0 })
effect(() => {
console.log('count:', state.count) // 初始执行,count变化时重新执行
})
state.count = 1 // 触发更新,打印count:1
2. 实现自定义指令 v-debounce(防抖)
vue
<script setup>
// 注册全局自定义指令
import { app } from '@/main'
app.directive('debounce', {
mounted(el, binding) {
const [fn, delay = 300] = binding.value
let timer = null
el.addEventListener('click', () => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => fn(), delay)
})
}
})
// 组件中使用
const handleSearch = () => {
console.log('搜索')
}
</script>
<template>
<button v-debounce="[handleSearch, 500]">搜索</button>
</template>
总结
核心考点回顾
- 基础层:data函数原因、v-model原理、computed/watch、v-if/v-show;
- 进阶层:响应式原理、虚拟DOM/diff、路由模式、Pinia vs Vuex;
- 实战层:性能优化、Composition API、跨域处理、手写代码。
答题技巧
- 基础题:先答核心逻辑,再补示例;
- 进阶题:结合原理+项目场景(如diff算法说key的实际坑);
- 手写题:先讲思路,再写核心代码,最后补边界处理。