
V1:Vue 有了数据响应式,为何还要 diff?
原因:
- 响应式只能解决“是否要更新”问题:当响应式数据变化时,Vue 知道“该重新渲染了”,但不知道具体要改哪里。
- Vue 的粒度不是单一变量:一个组件模板中可能有多个数据依赖,状态变化后需要重新执行 render 函数生成新的 VNode。
- diff 负责高效比较新旧 VNode 树:找出最小 DOM 操作,应用到真实 DOM。
- 性能优化关键:diff 使用双端对比 + 最长递增子序列算法,复杂度从 O(n³) 降到 O(n)。
总结:响应式负责调度,diff 负责精准更新。
V2:Vue 3 为什么不需要时间分片?
原因:
- 响应式粒度更细:Vue 通过 Proxy 精确知道哪个响应式数据变化,且只触发使用了该数据的组件重新渲染。
- 静态提升 + 补丁标记:编译期优化避免了大量 VNode 比较。
- 组件级调度:默认一个组件一个更新任务,粒度适合,不容易产生超长任务。
- 框架定位不同:Vue 设计为“渐进式”,默认不启用并发模式,代码量与心智负担更低。
React 为什么需要:不可变数据 + setState 模型下,一次更新可能涉及大量组件(任何依赖该状态的组件都重渲染),所以需要时间切片中断与优先级调度。
V3:Vue 3 为什么要引入 Composition API?
三大动机:
- 逻辑复用:Options API 中逻辑被拆到不同选项(data / methods / computed / mounted),复用难(mixin 冲突、来源不清)。Composition API 把逻辑聚合为函数,可随意复用。
- 更好的 TS 支持:Composition API 主要用函数与变量,与 TS 推断结合更好。
- 逻辑组织灵活性:Options API 按选项分类(横向),Composition API 按逻辑关注点分类(纵向),复杂组件更清晰。
示例对比:
// Options API
export default {
data() { return { count: 0 }; },
computed: { double() { return this.count * 2; } },
mounted() { console.log(this.count); }
};
// Composition API
import { ref, computed, onMounted } from 'vue';
export default {
setup() {
const count = ref(0);
const double = computed(() => count.value * 2);
onMounted(() => console.log(count.value));
return { count, double };
}
};V4:谈 Vue 事件机制,手写 $on、$off、$emit、$once
Vue 2 原型事件机制:在 Vue.prototype 上维护一个事件中心。
// 简化实现
class EventBus {
private events: Record<string, Function[]> = {};
$on(event: string, fn: Function) {
(this.events[event] ||= []).push(fn);
}
$off(event: string, fn?: Function) {
if (!this.events[event]) return;
if (!fn) { delete this.events[event]; return; }
this.events[event] = this.events[event].filter(f => f !== fn);
}
$emit(event: string, ...args: any[]) {
(this.events[event] || []).forEach(fn => fn(...args));
}
$once(event: string, fn: Function) {
const wrapper = (...args: any[]) => {
this.$off(event, wrapper);
fn(...args);
};
this.$on(event, wrapper);
}
}Vue 3 移除原因:官方推荐使用外部库(mitt / tiny-emitter)或 props/emit,理由是 Vue 实例本身已足够庞大。
V5:computed 计算值为什么可以依赖另一个 computed?
const a = ref(1);
const b = computed(() => a.value * 2);
const c = computed(() => b.value + 1); // 依赖 b,b 依赖 a
// a.value = 2; → b 更新 → c 自动更新原理:
- computed 是基于响应式依赖实现的特殊 effect;
- 当 computed 被访问时会进行依赖收集,依赖列表中包含所有被访问的响应式源(包括其他 computed);
- 被依赖的 computed 内部使用
dirty标记控制是否重新计算。
优势:自动依赖追踪,调用者可当作普通 ref 使用,无需手动订阅。
V6:说一下 vm.$set 原理
Vue 2 问题:Vue 2 通过 Object.defineProperty 劫持已有属性,无法检测新增属性和直接通过索引修改数组元素。
$set 实现原理:
function set(target: any, key: string | number, val: any) {
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key as number);
target.splice(key as number, 1, val);
return val;
}
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val;
}
const ob = target.__ob__;
if (!ob) { target[key] = val; return val; }
ob.defineReactive(target, key, val); // 关键:手动变为响应式
ob.dep.notify(); // 手动触发更新
return val;
}Vue 3 已解决:基于 Proxy,无需 $set,直接赋值即可响应式。
V7:怎么在 Vue 中定义全局方法?
Vue 2:
// 1. 挂到 Vue 原型
Vue.prototype.$http = axios;
// 2. 插件形式(推荐)
export default {
install(Vue) {
Vue.prototype.$utils = utils;
}
};
Vue.use(MyPlugin);Vue 3:
// 1. app.config.globalProperties
app.config.globalProperties.$http = axios;
// 2. provide / inject(推荐)
app.provide('http', axios);
// 在组件中
const http = inject('http');V8:Vue 中父组件怎么监听到子组件的生命周期?
Vue 2:通过 @hook:生命周期 事件。
<Child @hook:mounted="onChildMounted" />Vue 3:需要在子组件显式 emit:
<!-- Child.vue -->
<script setup>
const emit = defineEmits(['mounted']);
onMounted(() => emit('mounted'));
</script>
<!-- Parent.vue -->
<Child @mounted="onChildMounted" />更优方案:父组件可通过 ref 获取子组件实例,调用其暴露的方法(defineExpose)。
V9:vue 组件里写的原生 addEventListener 监听事件,要手动销毁吗?
必须手动销毁!
onMounted(() => {
window.addEventListener('resize', onResize);
});
onBeforeUnmount(() => {
window.removeEventListener('resize', onResize); // 必颁,否则内存泄露
});原因:Vue 只会在组件卸载时清理它自己绑定的事件(通过模板 @event),不会清理通过原生 API 绑定的事件。
Vue 3 setup 自动 cleanup:使用 useEventListener 库或自实现 hook。
V10:Vue 3 中的响应式设计原理
核心:Proxy + Reflect
function reactive<T extends object>(target: T): T {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key); // 依赖收集
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, key); // 派发更新
}
return result;
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
trigger(target, key);
return result;
}
});
}依赖收集:
- 每个响应式对象关联一个
dep(依赖容器); - 访问属性时记录当前激活的 effect;
- 收集为
dep.subs = Set<effect>。
派发更新:
- 属性变化时找到
dep,遍历subs依次执行 effect。
对比 Vue 2:
- Vue 2:Object.defineProperty 劫持已有属性,初始化递归遍历;
- Vue 3:Proxy 懒加载(访问才代理)、数组下标 / length / Map / Set 全部支持。
V11:Vue 中,created 和 mounted 钩子之间的时间受什么影响?
时间差 = created 钩子执行 → render 函数生成 VNode → 首次 patch → DOM 插入 → mounted 钩子执行
影响因素:
- 模板复杂度:模板越复杂、节点越多,渲染耗时越长;
- 组件嵌套深度:嵌套越多,patch 次数越多;
- 同步子组件状态:子组件 init / mounted 也会占用时间;
- 首次渲染数据计算:复杂的 computed、watch 初始化;
- 业务逻辑:created 中同步耗时操作(同步请求、复杂计算)会卡在这一阶段。
优化建议:
- created 只做轻量初始化(state 准备、事件绑定);
- 耗时逻辑放
onMounted或异步加载。
V12:Vue 中,推荐在哪个生命周期发起请求?
推荐:created 或 setup 中
原因:
- 提前请求:在 DOM 渲染前发起,能提前获取数据,减少首屏加载时间;
- SSR 友好:服务端渲染时 created 会执行,而 mounted 不会(服务器无 DOM);
- 依赖收集:created 阶段访问数据也能被响应式系统追踪。
需要等待 DOM 的场景才用 mounted(如获取元素尺寸、绑定 DOM 事件)。
// 推荐写法
export default {
async created() {
const res = await fetchData();
this.list = res.data;
}
};V13:为什么 React 需要 Fiber 架构,而 Vue 却不需要?
React 面临的问题:
- 数据不可变,状态变化触发整个组件子树重渲染;
- 需要调度器决定优先级、可以中断与恢复。
Vue 天然优势:
- 响应式粒度细:Proxy 精确追踪到哪个组件、哪个属性变化,只重新渲染实际依赖该数据的组件;
- 编译期优化:静态节点提升、补丁标记,减少 VNode 比较;
- 架构简单:不需要时间切片、不需要并发模式也能保持良好性能。
总结:React 是“重调度 + 轻响应式”,Vue 是“重响应式 + 轻调度”。
V14:SPA 首屏加载速度慢怎么解决?
八种优化手段:
- 包体积优化:路由懒加载、按需引入第三方库、Tree Shaking、压缩代码(terser / esbuild)。
- 资源优化:CDN、图片压缩(WebP/AVIF)、雪碧图、内联关键 CSS。
- 缓存策略:HTTP 强缓存 + 协商缓存、Service Worker、IndexedDB。
- 网络优化:HTTP/2 多路复用、域名分片、预解析 DNS、预连接(preconnect)、预加载(preload)。
- SSR / SSG:Nuxt / Next.js、服务端预渲染、静态化。
- 骨架屏 / Loading:优化感知体验。
- 首屏数据预取:路由进入前预取数据。
- 监控 & 调优:Lighthouse、WebPageTest、PerformanceObserver。
V15:说下 Vite 的原理
核心:浏览器原生 ESM + esbuild 预构建
冷启动:
- 预构建依赖(基于 esbuild):用
esbuild将node_modules中的 CommonJS / UMD 模块转换为 ESM(快 10-100 倍)。 - 按需编译:浏览器请求哪个模块,Vite 才用
esbuild转换该模块(转换与请求并行),返回后浏览器继续请求依赖模块。 - 缓存:强缓存(HTTP 头)+ 协商缓存(304)。
热更新 HMR:
- 通过 WebSocket 监听文件变化;
- 变动的模块被
esbuild重新编译; - 框架插件接收更新(vue-loader / react-refresh),热替换运行中的模块。
生产构建:使用 Rollup(生态成熟、产物优化好)。
为什么快:
- 不走打包:无需全量构建;
- 按需编译:用哪个编哪个;
- 原生 ESM:浏览器原生支持,无需 polyfill。
V16:Vue 2 为什么不能检测数组变化?怎么解决?
原因:Vue 2 通过 Object.defineProperty 劫持,数组的以下操作不会被检测:
- 直接通过索引赋值:
arr[0] = 1; - 修改 length:
arr.length = 0; - 部分方法(Vue 2 已重写了 7 个变更方法:
push/pop/shift/unshift/splice/sort/reverse)。
解决方案:
// 1. Vue.set / this.$set
this.$set(this.arr, index, value);
// 2. 替换数组(推荐)
this.arr.splice(index, 1, value);
// 3. 整体重新赋值
this.arr = [...this.arr.slice(0, index), value, ...this.arr.slice(index + 1)];Vue 3 已根治:基于 Proxy,所有数组操作天然可追踪。
V17:说说 Vue 页面渲染流程
Vue 3 渲染流程:
- 编译阶段:模板 → AST → 渲染函数(带静态提升、补丁标记优化)。
挂载阶段(首次渲染):
- 创建组件实例(setup);
- 执行 render 函数生成 VNode 树;
- 调用
patch函数; - 首次 patch走挂载逻辑:创建真实 DOM、插入到容器、绑定事件;
- 触发
onMounted钩子。
更新阶段:
- 响应式数据变化触发 effect;
- 调度器(scheduler)将更新加入微任务队列(nextTick);
- 重新执行 render 函数生成新 VNode;
- 调用
patch函数进行 diff 比较; - 将差异(DOM 操作)应用到真实 DOM;
- 触发
onUpdated钩子。
- 卸载阶段:触发
onBeforeUnmount/onUnmounted,移除 DOM、解绑事件、清理 effect。
V18:Vue 中 computed 和 watch 区别
| 维度 | computed | watch |
|---|---|---|
| 用途 | 派生新值 | 监听数据变化执行副作用 |
| 缓存 | 有缓存,依赖不变不重算 | 无缓存 |
| 返回 | 必须 return | 不需要 |
| 异步 | 不支持异步 | 支持异步 |
| 调用时机 | 访问时惰性求值 | 数据变化立即/深度监听 |
| 适用 | 模板渲染、数据转换 | 路由变化、数据持久化、接口请求 |
// computed
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
// watch
watch(searchKey, async (newVal) => {
const res = await api.search(newVal);
list.value = res.data;
}, { debounce: 300, immediate: true });V19:Vuex 中的辅助函数怎么使用?
辅助函数:mapState、mapGetters、mapMutations、mapActions、createNamespacedHelpers。
// Options API 中
import { mapState, mapMutations } from 'vuex';
export default {
computed: {
...mapState(['count']),
...mapState('user', ['name'])
},
methods: {
...mapMutations(['increment']),
...mapActions(['fetchData'])
}
};
// Composition API 中(推荐)
import { useStore } from 'vuex';
import { computed } from 'vue';
export default {
setup() {
const store = useStore();
return {
count: computed(() => store.state.count),
increment: () => store.commit('increment')
};
}
};Vue 3 推荐 Pinia:更轻量、Composition API 友好、TypeScript 友好。
V20:用 Vue 3 实现一个 Modal,怎么设计?
设计要点:
- Teleport 渲染到 body:避免被父元素裁剪 / 滚动;
- 受控 / 非受控:
v-model双向绑定显隐; - 事件:
open/close/confirm/cancel; - 插槽:title、default、footer;
- 可配置:尺寸、遮罩、点击遮罩关闭、ESC 关闭、滚动锁定。
<template>
<Teleport to="body">
<Transition name="modal">
<div v-if="modelValue" class="modal-mask" @click.self="close">
<div class="modal" :style="{ width: width }">
<header v-if="$slots.title">
<slot name="title" />
<button @click="close">×</button>
</header>
<main><slot /></main>
<footer v-if="$slots.footer">
<slot name="footer" :confirm="confirm" :cancel="cancel" />
</footer>
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup lang="ts">
const props = defineProps<{ modelValue: boolean; width?: string }>();
const emit = defineEmits(['update:modelValue', 'confirm', 'cancel']);
const close = () => emit('update:modelValue', false);
const confirm = () => emit('confirm');
const cancel = () => emit('cancel');
const onKey = (e: KeyboardEvent) => e.key === 'Escape' && close();
watch(() => props.modelValue, (v) => {
document.body.style.overflow = v ? 'hidden' : '';
if (v) document.addEventListener('keydown', onKey);
else document.removeEventListener('keydown', onKey);
});
</script>V21:Vue 3 Tree Shaking 特性是什么?举例说明
原理:ES Module 静态结构使得打包工具可以分析哪些导出未被使用,删除未引用的代码。
Vue 3 的优势:
- 全部 API 采用 ESM 导出,按需引入;
- 编译器、运行时、服务端渲染分离为独立包;
v-model/ 自定义指令等按功能分包。
// 只引入需要的 API(Tree-shakable)
import { ref, computed, onMounted } from 'vue';
// 未被引入的 API(如 v-show / Transition)不会出现在最终 bundle 中对比 Vue 2:Vue 2 将所有 API 挂载到 Vue 单例上(Vue.nextTick、Vue.set),打包工具无法静态分析,默认全部打包进去。
V22:Vue 3 Composition API vs Vue 2 Options API
| 维度 | Options API | Composition API |
|---|---|---|
| 组织方式 | 按选项分类(data/methods/...) | 按逻辑关注点聚合 |
| 代码复用 | mixin(覆盖/来源不清晰) | 自定义 Hook 函数 |
| TS 推断 | 一般 | 优秀 |
| 学习曲线 | 低,上手快 | 中,需要理解响应式原理 |
| 适用场景 | 小型组件、简单逻辑 | 复杂组件、大型企业应用 |
| 响应式原理透明性 | 隐藏 | 需手动管理 |
推荐策略:复杂组件用 Composition API,简单展示组件用 Options API(Vue 3 同时支持)。
V23:Vue 3 性能提升主要体现在哪几方面?
- 响应式系统升级:Proxy 替代 defineProperty,支持数组下标 / Map / Set,新增属性自动响应式。
编译期优化:
- 静态节点提升(PatchFlag);
- 事件监听缓存;
- 树摇友好(按需引入)。
- VNode 优化:单个 VNode 体积减少,patch 更快。
- SSR 优化:
@vue/server-renderer流式输出。 - 体积更小:核心 ~34KB(Vue 2 ~50KB),Tree Shaking 进一步减少实际包体积。
- Composition API:逻辑复用更高效,减少 mixin 带来的不必要渲染。
V24:Vue 3 的设计目标是什么?做了哪些优化?
设计目标:
- 更好的 TS 支持:大型项目类型安全;
- 更好的逻辑复用:解决 mixin 缺陷;
- 更好的性能:编译期 + 运行时全面优化;
- 更小的体积:Tree-shakable;
- 更灵活的 API:同时支持 Options 和 Composition;
- 为未来铺路:为 Vapor 模式(无 VNode)和自定义渲染器打基础。
主要优化:
| 维度 | 优化手段 |
|---|---|
| 响应式 | Proxy 替代 defineProperty |
| 编译 | PatchFlag、静态提升、缓存事件 |
| Diff | 最长递增子序列算法减少 DOM 移动 |
| SSR | 流式渲染、组件级缓存 |
| 包体积 | Tree Shaking、按需打包 |