vue 原理层
通义千问 vue原理
Vue 的底层原理面试题通常聚焦于其响应式系统、虚拟 DOM、编译过程、组件机制等核心实现。以下是高频且深度的 Vue(以 Vue 2 和 Vue 3 为主)底层原理面试题及解析,适合中高级前端工程师准备:
1. Vue 的响应式原理是什么?Vue 2 和 Vue 3 有何区别?
答:
Vue 2:
基于 Object.defineProperty 实现;
递归遍历 data 对象,为每个属性定义 getter/setter;
缺陷:
无法监听新增/删除属性(需 $set / $delete);
数组索引修改和长度变更无法触发更新;
初始化时递归遍历性能开销大。
Vue 3:
使用 Proxy + Reflect 重写响应式系统(reactive);
支持监听对象属性的增删、数组索引修改、Map/Set 等;
惰性收集依赖(只有访问过的属性才建立响应联系);
性能更好,代码更简洁。
核心思想:依赖收集 + 派发更新。在 getter 中收集依赖(Watcher / effect),在 setter 中通知更新。
2. Vue 是如何实现依赖收集和派发更新的?
答:
依赖收集:
当组件渲染时,会读取响应式数据(触发 getter);
此时将当前的 Watcher(Vue 2)或 effect(Vue 3) 添加到该属性的依赖列表(dep)中。
派发更新:
当数据变化(触发 setter),通知所有依赖该属性的 Watcher/effect;
Watcher 触发组件重新 render,生成新的 VNode;
通过 patch 算法对比新旧 VNode,更新真实 DOM。
Vue 2 中:Dep 类管理依赖,Watcher 表示一个观察者(如组件渲染函数);
Vue 3 中:使用 effect 函数包裹响应式逻辑,通过 track / trigger 实现依赖追踪与触发。
3. Vue 的 nextTick 原理是什么?
答:
nextTick 用于在 DOM 更新完成后执行回调;
原理:利用 JavaScript 的事件循环机制,将回调放入 microtask 或 macrotask 队列;
优先使用 Promise.then(microtask);
兼容降级:MutationObserver → setImmediate → setTimeout;
Vue 在每次数据变更后,会将 DOM 更新操作推入一个异步队列,并在下一个 tick 执行;
多次调用 nextTick 会合并回调,避免重复刷新。
4. Vue 的虚拟 DOM 和 diff 算法是如何工作的?
答:
Vue 使用 VNode(虚拟节点) 描述 UI 结构;
渲染过程:模板 → AST → render 函数 → VNode → 真实 DOM;
diff 策略(与 React 类似但更优化):
同层比较:只对比同一层级节点,不跨层级;
key 的作用:通过 key 快速复用已有节点,避免不必要的创建/销毁;
双端对比算法(Vue 2):
同时从新旧 children 的头尾两端进行比较(4 种情况);
提高列表更新效率,尤其适用于列表移动场景;
Vue 3 进一步优化 patchFlag 和静态提升,减少 diff 范围。
5. Vue 的编译过程(template → render function)是怎样的?
答:(以 Vue 2 为例)
- parse:将 template 字符串解析为 AST(抽象语法树);
- optimize:标记静态节点(static/staticRoot),后续更新可跳过;
- generate:将 AST 转换为 render 函数字符串(codegen);
最终生成类似 _c(‘div’, { attrs: {…} }, […]) 的函数调用; - 在运行时通过 new Function() 执行 render 函数,返回 VNode。
Vue 3 的编译器支持更多优化:如 PatchFlags、hoistStatic、cacheHandlers 等,提升运行时性能。
6. 为什么 Vue 3 要用 Proxy 替代 Object.defineProperty?
答:
功能更强大:
可监听对象属性的添加/删除;
支持数组索引修改和 length 变更;
支持 Map、Set、WeakMap 等数据结构。
性能更好:
不需要递归遍历整个对象(惰性代理);
动态属性自动响应,无需手动 $set。
语义更清晰:Proxy 是 ES6 标准,更符合现代 JS 编程范式。
缺点:Proxy 无法 polyfill,不支持 IE11(Vue 3 放弃 IE 支持)。
7. Vue 组件的生命周期(Vue 2)和组合式 API(Vue 3)执行顺序?
答:
Vue 2 生命周期钩子顺序:
beforeCreate → created →
beforeMount → mounted →
beforeUpdate → updated →
beforeUnmount → unmounted
Vue 3 setup() 与生命周期关系:
setup() 在 beforeCreate 之前执行;
onMounted 等组合式钩子等价于选项式钩子;
所有组合式 API(ref, reactive, computed)都在 setup 中声明。
注意:setup 中没有 this,所有数据和方法需显式 return 才能在模板中使用。
8. computed 和 watch 的实现原理?
答:
computed:
本质是一个 lazy 的 effect(Vue 3)或 Watcher(Vue 2);
具有缓存性:依赖不变时直接返回缓存值;
通过 dirty 标志位控制是否重新计算。
watch:
监听响应式数据变化,触发回调;
Vue 2:基于 Watcher 实现;
Vue 3:基于 effect + scheduler 实现,支持 deep、immediate 等选项;
watchEffect 自动收集依赖,无需指定监听源。
9. Vue 如何实现组件通信?底层机制是什么?
答:
props / $emit:父子通信,基于组件实例的 $props 和自定义事件系统;
$parent / $children / refs:直接访问组件实例(不推荐);
provide / inject:依赖注入,祖先组件通过 provide 注入数据,后代通过 inject 获取(非响应式需配合 ref/reactive);
Vuex / Pinia:全局状态管理,基于响应式 store;
事件总线(EventBus):基于 $on / $emit(Vue 2),Vue 3 需自行实现或使用 mitt。
底层:所有通信最终都依赖 响应式系统 + 事件机制。
10. Vue 3 的 Composition API 优势是什么?如何实现逻辑复用?
答:
优势:
更好的逻辑组织(按功能而非选项);
更强的类型推导(TypeScript 友好);
更灵活的逻辑复用(自定义 hooks)。
逻辑复用:
通过 自定义组合函数(composables) 封装通用逻辑;
利用 ref / reactive / computed / watch 等构建可复用单元;
示例:useMouse()、useLocalStorage()。
底层:组合式 API 本质是函数封装,依赖 Vue 3 的响应式系统自动追踪依赖。
如果你希望深入某个方向(比如 Vue 3 响应式源码分析、patchFlags 详解、编译器 AST 节点结构等),可以继续提问!
deepseek Vue 底层原理
1. 响应式原理
题目:Vue 2 和 Vue 3 的响应式原理有什么区别?
参考答案:
Vue 2 响应式原理:
- 使用
Object.defineProperty实现数据劫持 - 对对象的每个属性进行递归劫持
- 数组通过重写原型方法实现响应式
// Vue 2 简化实现
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`get ${key}: ${val}`);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log(`set ${key}: ${newVal}`);
val = newVal;
// 触发更新
dep.notify();
}
}
});
}
Vue 3 响应式原理:
- 使用
Proxy实现数据代理 - 对整个对象进行代理,无需递归初始化
- 惰性代理,只在访问时进行代理
// Vue 3 简化实现
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
track(target, key); // 依赖收集
return typeof res === 'object' ? reactive(res) : res;
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发更新
return res;
}
});
}
2. 虚拟 DOM 和 Diff 算法
题目:Vue 的虚拟 DOM Diff 算法有什么特点?
参考答案:
Vue 的 Diff 算法采用双端比较策略:
- 同层比较:只比较同一层级的节点
- 双指针:使用四个指针进行新旧节点的比较
- Key 优化:通过 key 识别可复用的节点
- 就地复用:尽可能复用相同类型的 DOM 元素
Diff 过程:
- 头头比较
- 尾尾比较
- 头尾比较
- 尾头比较
- key 映射查找
// 简化的 Diff 算法核心
function updateChildren(parentElm, oldCh, newCh) {
let oldStartIdx = 0, newStartIdx = 0;
let oldEndIdx = oldCh.length - 1, newEndIdx = newCh.length - 1;
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
// 四种比较情况
if (sameVnode(oldCh[oldStartIdx], newCh[newStartIdx])) {
// 头头相同
patchVnode(oldCh[oldStartIdx], newCh[newStartIdx]);
oldStartIdx++;
newStartIdx++;
} else if (sameVnode(oldCh[oldEndIdx], newCh[newEndIdx])) {
// 尾尾相同
patchVnode(oldCh[oldEndIdx], newCh[newEndIdx]);
oldEndIdx--;
newEndIdx--;
}
// ... 其他情况
}
}
3. 编译过程
题目:Vue 模板是如何编译成渲染函数的?
参考答案:
Vue 的编译过程分为三个阶段:
1. 解析(Parse)
- 将模板字符串解析成 AST(抽象语法树)
- 使用 HTML 解析器解析标签
- 使用文本解析器解析插值表达式
2. 优化(Optimize)
- 标记静态节点和静态根节点
- 在后续更新中跳过静态子树
3. 生成(Generate)
- 将 AST 转换为渲染函数代码
- 生成
_c、_v、_s等帮助函数调用
// 模板: <div>{{ message }}</div>
// 编译结果
function render() {
return _c('div', [_v(_s(message))]);
}
4. 组件化原理
题目:Vue 组件是如何创建和初始化的?
参考答案:
组件创建流程:
- 组件定义:通过 Vue.component 或单文件组件定义
- 组件配置合并:合并全局配置和组件配置
- 组件构造函数:创建 Vue 子类
- 组件实例化:创建组件实例并初始化
// 组件初始化核心代码
Vue.extend = function (extendOptions) {
const Super = this;
const Sub = function VueComponent(options) {
this._init(options);
};
// 原型继承
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
// 合并配置
Sub.options = mergeOptions(Super.options, extendOptions);
return Sub;
};
5. 生命周期原理
题目:Vue 的生命周期是如何实现的?
参考答案:
Vue 生命周期通过发布订阅模式实现:
- 生命周期钩子数组:每个生命周期对应一个钩子函数数组
- 调用时机:在特定阶段遍历执行对应钩子数组
- 合并策略:父子组件的生命周期钩子会合并
// 生命周期调用实现
function callHook(vm, hook) {
const handlers = vm.$options[hook];
if (handlers) {
for (let i = 0; i < handlers.length; i++) {
try {
handlers[i].call(vm);
} catch (e) {
handleError(e, vm, `${hook} hook`);
}
}
}
}
// 在初始化过程中调用
callHook(vm, 'beforeCreate');
initInjections(vm);
initState(vm);
initProvide(vm);
callHook(vm, 'created');
6. 依赖收集与派发更新
题目:描述 Vue 的依赖收集和派发更新过程
参考答案:
依赖收集(收集依赖):
- 在 getter 中收集当前正在计算的 watcher
- 建立数据属性与 watcher 的多对多关系
- 每个属性有一个 dep 实例管理所有依赖
派发更新(通知更新):
- 在 setter 中通知 dep 的所有 watcher
- watcher 将自身加入异步更新队列
- 下一个 tick 时批量执行 watcher 更新
// 简化的依赖收集实现
class Dep {
constructor() {
this.subs = [];
}
depend() {
if (Dep.target) {
Dep.target.addDep(this);
}
}
notify() {
const subs = this.subs.slice();
for (let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
// Watcher 实现
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm;
this.getter = expOrFn;
this.cb = cb;
this.value = this.get();
}
get() {
Dep.target = this;
const value = this.getter.call(this.vm);
Dep.target = null;
return value;
}
update() {
queueWatcher(this);
}
}
7. NextTick 原理
题目:Vue.nextTick 的实现原理是什么?
参考答案:
nextTick 利用 JavaScript 的事件循环机制:
- 微任务优先:优先使用 Promise.then
- 降级策略:不支持 Promise 时降级到 MutationObserver、setImmediate、setTimeout
- 回调队列:将回调函数收集到队列中,在下一个 tick 执行
const callbacks = [];
let pending = false;
function nextTick(cb, ctx) {
callbacks.push(() => {
try {
cb.call(ctx);
} catch (e) {
handleError(e, ctx, 'nextTick');
}
});
if (!pending) {
pending = true;
// 异步执行
timerFunc();
}
}
// 定义异步方法
let timerFunc;
if (typeof Promise !== 'undefined') {
const p = Promise.resolve();
timerFunc = () => {
p.then(flushCallbacks);
};
} else if (typeof MutationObserver !== 'undefined') {
// 降级到 MutationObserver
} else {
// 降级到 setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
8. 计算属性和侦听器
题目:computed 和 watch 的实现原理有什么区别?
参考答案:
computed 实现原理:
- 基于响应式依赖进行缓存
- 惰性求值,只有依赖变化时才重新计算
- 每个计算属性都是一个 watcher
watch 实现原理:
- 监听特定数据的变化
- 立即执行或延迟执行回调
- 支持深度监听和立即执行
// computed 实现简化
function initComputed(vm, computed) {
const watchers = vm._computedWatchers = Object.create(null);
for (const key in computed) {
const userDef = computed[key];
const getter = typeof userDef === 'function' ? userDef : userDef.get;
// 创建 computed watcher
watchers[key] = new Watcher(vm, getter, noop, { lazy: true });
// 定义 computed 属性
defineComputed(vm, key, userDef);
}
}
9. 指令系统
题目:Vue 指令是如何解析和执行的?
参考答案:
指令解析流程:
- 编译时解析:在模板编译阶段解析指令
- 生成指令对象:为每个指令创建指令对象
- 生命周期钩子:指令有 bind、inserted、update 等生命周期
- 运行时执行:在对应时机调用指令钩子函数
// 指令处理核心
function updateDirectives(oldVnode, vnode) {
if (oldVnode.data.directives || vnode.data.directives) {
_update(oldVnode, vnode);
}
}
function _update(oldVnode, vnode) {
const isCreate = oldVnode === emptyNode;
const isDestroy = vnode === emptyNode;
const oldDirs = normalizeDirectives(oldVnode.data.directives);
const newDirs = normalizeDirectives(vnode.data.directives);
for (const key in newDirs) {
const dir = newDirs[key];
if (!oldDirs[key]) {
// 新指令,调用 bind
callHook(dir, 'bind', vnode, oldVnode);
} else {
// 更新指令,调用 update
dir.oldValue = oldDirs[key].value;
callHook(dir, 'update', vnode, oldVnode);
}
}
}
10. 插件系统
题目:Vue.use 插件机制的实现原理是什么?
参考答案:
插件机制原理:
- install 方法:插件必须提供 install 方法
- 单次安装:避免重复安装同一插件
- 参数传递:接收 Vue 构造函数和可选参数
// Vue.use 实现
Vue.use = function (plugin) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
// 避免重复安装
if (installedPlugins.indexOf(plugin) > -1) {
return this;
}
// 附加参数
const args = toArray(arguments, 1);
args.unshift(this);
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args);
} else if (typeof plugin === 'function') {
plugin.apply(null, args);
}
installedPlugins.push(plugin);
return this;
};
这些底层原理的理解有助于在 Vue 面试中展示深度技术认识,同时在实际开发中能够更好地进行性能优化和问题排查。
文心一言
在 Vue 底层原理相关的面试中,可能会涉及响应式原理、虚拟 DOM、生命周期、组件通信、性能优化等多个方面,以下是一些典型的面试题及解析:
响应式原理
问题:Vue 2 和 Vue 3 的响应式原理分别是什么?它们有什么区别?
解析:
Vue 2:使用 Object.defineProperty() 方法来劫持对象属性的 getter 和 setter,从而实现数据的响应式更新。在初始化时,Vue 会递归遍历 data 对象的所有属性,并为每个属性添加 getter 和 setter。当访问或修改这些属性时,会触发相应的 getter 或 setter,进而实现依赖收集和派发更新。
Vue 3:改用 Proxy 对象来代理整个对象,从而实现对对象及其子属性的劫持。Proxy 可以监听对象属性的新增、删除以及数组下标的变化,克服了 Object.defineProperty() 的一些局限性。同时,Proxy 的性能也更优。
虚拟 DOM
问题:Vue 为什么要使用虚拟 DOM?虚拟 DOM 的优势是什么?
解析:
原因:直接操作真实 DOM 的成本较高,频繁的 DOM 操作会导致页面性能下降。虚拟 DOM 是一个轻量级的 JavaScript 对象,它模拟了真实 DOM 的结构。Vue 通过比较新旧虚拟 DOM 的差异,以最小的代价更新真实 DOM,从而提高性能。
优势:减少直接操作真实 DOM 的开销;通过 Diff 算法批量更新,提升渲染效率;可以跨平台使用(如服务端渲染、小程序等)。
生命周期
问题:请描述 Vue 组件的生命周期方法及其作用。
解析:
创建阶段:
beforeCreate:实例初始化后,数据观测和事件配置之前调用。
created:实例创建完成后调用,此时可以访问数据和方法,但 DOM 还未挂载。
挂载阶段:
beforeMount:挂载开始之前调用,此时模板已编译完成,但还未挂载到 DOM 上。
mounted:实例挂载到 DOM 上后调用,此时可以访问 DOM 元素。
更新阶段:
beforeUpdate:数据更新时调用,此时 DOM 还未重新渲染。
updated:数据更新导致 DOM 重新渲染后调用。
销毁阶段:
beforeDestroy:实例销毁之前调用,此时可以执行一些清理工作。
destroyed:实例销毁后调用,此时所有绑定的事件和子组件都已被解绑。
组件通信
问题:Vue 组件之间有哪些通信方式?它们的原理是什么?
解析:
父子组件通信:
Props:父组件通过 props 向子组件传递数据。
**
emit∗∗:子组件通过‘
emit触发自定义事件,父组件通过v-on` 监听这些事件。
兄弟组件通信:
Event Bus:通过创建一个全局的事件总线对象,实现兄弟组件之间的通信。
Vuex:使用 Vuex 状态管理库,实现全局状态共享。
跨层级组件通信:
Provide/Inject:祖先组件通过 provide 提供数据,后代组件通过 inject 注入数据。
Vuex:同样可以使用 Vuex 实现跨层级组件通信。
性能优化
问题:Vue 应用中有哪些性能优化技巧?
解析:
合理使用 v-if 和 v-show:v-if 适用于条件渲染,v-show 适用于频繁切换显示/隐藏的场景。
使用 keep-alive 缓存组件:避免不必要的重新渲染,提高性能。
代码分割和懒加载:使用动态导入实现组件的懒加载,减少初始加载时间。
避免不必要的响应式数据:使用 Object.freeze() 冻结不需要响应式的数据,减少监听开销。
使用虚拟列表:对于长列表数据,使用虚拟列表技术只渲染可见部分的数据。
KIMI vue原理
下面给出一份「Vue 底层原理面试题清单 + 核心答案深度剖析」,覆盖 响应式、虚拟 DOM、模板编译、渲染流程、组件系统、性能优化、3.x 新架构 等高频考点。
每道题都先给「面试官想听的 30 秒答案」,再给出「底层原理级扩展」,可直接背诵也可深入源码。
1. 响应式系统篇
1.1 Vue2 响应式原理(Object.defineProperty)
30 秒答案
- 递归遍历 data,用
Object.defineProperty把属性转成getter/setter - 每个组件实例对应一个 Watcher;getter 阶段把当前 Watcher 收集到 Dep 里,setter 阶段通知 Dep 重新渲染。
- 缺陷:只能拦截已存在属性;数组下标/长度变更需重写 7 个变异方法;深度递归性能损耗。
底层扩展
- Dep 与 Watcher 是多对多关系:一个响应式字段可能对应多个视图(v-for、计算属性),一个视图可能依赖多个字段。
- 通过 Dep.target 全局栈维护嵌套收集过程,保障父子组件、计算属性、watch 依赖正确。
- 数组拦截:把
push/pop/splice等 7 个方法用函数劫持重写,内部先执行原生逻辑再手动dep.notify()。
1.2 Vue3 响应式原理(Proxy + Reflect)
30 秒答案
- 用
new Proxy(target, handler)代理整个对象,可拦截 13 种 操作(get/set/deleteProperty/has/ownKeys…),无需递归。 - 依赖收集用 WeakMap + Map 做二级索引:
target → key → Set<effect>,内存更省,支持动态增删属性。 - 嵌套对象只在访问时再 reactive,懒代理解决性能痛点;数组下标/长度天然支持。
底层扩展
effect函数就是 Vue3 的 Watcher,track() 收集,trigger() 触发。- computed / watch / render 统一基于 ReactiveEffect 调度,配合 scheduler 选项实现 nextTick 批处理。
- 原始对象脱敏:通过
toRaw()可拿到未代理对象,避免循环引用。
2. 模板编译篇
2.1 模板→渲染函数全流程
30 秒答案
parse → transform → codegen
- parse:把 template 字符串生成 AST(带位置信息、属性、指令)。
- transform:遍历 AST 做静态提升(hoistStatic)、静态节点标记(PatchFlag)、预字符串化(stringifyStatic)。
- codegen:递归 AST 生成
function render(_ctx, _cache) { return h(...) }函数字符串,走new Function生成可执行渲染函数。
底层扩展
- 静态提升:把纯静态子树提升到渲染函数外,每次复用同一 VNode,跳过 diff。
- PatchFlag(Vue3 核心优化):
- 动态文本
1,动态 class2,动态 style4… 用位运算标记,diff 时只比对指定位。
- 动态文本
- Block Tree:带动态子节点的根称为 Block,收集子代动态节点数组,diff 时 O(1) 定位,无需递归整棵树。
3. 虚拟 DOM & Diff 篇
3.1 Vue2 Diff 算法
30 秒答案
- 双端比较:新旧首尾 4 个指针,向中间靠拢;O(n)。
- 无法复用时建 key→index HashMap,再遍历旧列表定位节点;判断 移动/创建/删除。
- 无 key 时直接 就地复用 顺序,可能导致状态错位。
3.2 Vue3 Diff 算法(最长递增子序列 LIS)
30 秒答案
- 前置/后置元素快速剪掉相同头尾 → 剩余无序部分。
- 用 key 建立 新列表索引图 → 遍历旧列表生成 新索引数组;值为 0 表示需新建。
- 对索引数组求 LIS,得到最长不需要移动的子序列,其余节点做最少量 移动/插入/删除;O(n log n)。
4. 组件系统 & 生命周期
4.1 组件实例化流程(Vue3)
30 秒答案
- 解析组件选项 → 创建组件VNode(带shapeFlag=COMPONENT)
- renderer 遇到 COMPONENT 走
mountComponent:- 新建组件实例(
const instance = { vnode, ctx, props, slots, emit, setupState, ... }) - 调用
setup()拿到返回值,填充setupState/render - 建立渲染 effect,执行
subTree = render()→ 递归 patch
- 新建组件实例(
- 生命周期:beforeCreate/created 被 setup 语法糖隐藏;onBeforeMount、onMounted 在
invokeArrayFns中顺序触发。
4.2 依赖注入 provide/inject 原理
30 秒答案
- 父组件在
provides对象写值,子组件通过inject(key, default)向上查 父链 prototype(Vue3 用Object.create(parentProvides)形成原型链),支持应用级、组件级多层覆盖。
5. 性能优化 & 编译时 trick
- v-once 把节点标记为静态,后续直接复用 VNode。
- v-memo(Vue3.2)给列表项加缓存键,命中时跳过整个子树 diff。
- shallowReactive/shallowRef 只对根层做代理,嵌套对象不变,减少 Proxy 创建开销。
- Tree-shaking:Vue3 核心 API 全部 ESModule 导出,未引用代码可被 Rollup/esbuild 摇掉,运行时最小 13 kB。
6. Vue3 新架构总览(用于压轴回答)
“Vue3 从编译时+运行时双视角重构:
编译层把模板转换成 带 PatchFlag 的 Block Tree,并做静态提升、预字符串化;
运行层用 Proxy 实现懒代理响应式,LIS Diff 减少 DOM 操作,Composition API 让逻辑复用不再依赖高阶组件/mixin;
打包体积通过 Tree-shaking + 按需引入,核心运行时压缩后仅 13 kB;
加上 TypeScript 完全重写,类型提示和 IDE 体验大幅提升。”
7. 高频手写/源码级追问
- 手写 reactive/ref/effect 迷你版(要求支持 track/trigger)
- 实现一个 computed,演示懒计算与缓存
- 实现 nextTick(微任务队列 + Promise 降级)
- 实现 watch,支持 immediate/deep
- 实现 keep-alive 组件(LRU 缓存 vnode + include/exclude 匹配)
使用方式
- 背答案:直接背诵「30 秒答案」应对 80% 面试。
- 源码级:把「底层扩展」结合 Vue3 仓库(
packages/reactivity、runtime-core、compiler-core)打断点调试,轻松应对“看过源码吗”式追问。
需要哪一道题的完整源码实现或调试演示,再@我!
豆包 vue 原理
Vue 底层原理是面试中的高频考点,以下是常见的面试题及核心解析:
1. Vue 的响应式原理是什么?
核心逻辑:
Vue 通过 数据劫持 和 依赖收集 实现响应式,核心步骤包括:
- 数据劫持:使用
Object.defineProperty(Vue 2)或Proxy(Vue 3)拦截对象的get(读取)和set(修改)操作。 - 依赖收集:在数据被读取时(
get),收集当前依赖(即使用该数据的组件/副作用函数);在数据被修改时(set),触发依赖更新(重新执行副作用函数,如组件重新渲染)。
Vue 2 与 Vue 3 区别:
- Vue 2 对对象新增/删除属性、数组下标修改无法劫持(需用
Vue.set/this.$set); - Vue 3 用
Proxy直接代理整个对象,天然支持对象动态属性和数组修改,且能监听Map/Set等数据结构。
2. Vue 2 中 Object.defineProperty 的缺陷及解决方式?
缺陷:
- 只能劫持对象的 已有属性,新增/删除属性无法触发更新;
- 无法监听数组的 下标修改(如
arr[0] = 1)和 长度变化(如arr.length = 0)。
解决方式:
- 提供
Vue.set(obj, key, value)方法,手动触发依赖更新; - 重写数组的 7 个变更方法(
push/pop/shift/unshift/splice/sort/reverse),在方法内部触发更新。
3. 什么是虚拟 DOM(Virtual DOM)?为什么用它?
定义:
虚拟 DOM 是用 JavaScript 对象描述真实 DOM 的结构(如 { tag: 'div', props: {}, children: [] }),是对真实 DOM 的抽象。
作用:
- 减少 DOM 操作:通过对比新旧虚拟 DOM 的差异(diff 算法),只更新变化的部分,避免全量重绘;
- 跨平台兼容:虚拟 DOM 可渲染到不同平台(浏览器 DOM/小程序/Canvas 等),实现“一次编写,多端运行”。
4. Vue 的 diff 算法原理?
核心逻辑:
Vue 的 diff 算法是 同层比较(只对比同一层级的节点,不跨层级比较),流程如下:
- 先判断节点是否相同(
key和tag一致),不同则直接替换; - 若节点相同,对比属性(
props)和事件,更新差异; - 对比子节点,通过 双指针法 高效处理常见场景(如头/尾新增、删除、移动):
- 先比较首尾节点,若匹配则移动指针;
- 若不匹配,通过
key建立映射表查找可复用节点,减少移动操作。
key 的作用:key 是节点的唯一标识,帮助 diff 算法快速定位可复用节点,避免误复用(如列表渲染时,不用索引作 key 可防止数据错乱)。
5. Vue 的生命周期及对应的内部逻辑?
核心生命周期:
beforeCreate:实例初始化后,数据观测(data/props)和事件初始化前,无法访问data;created:数据观测完成,可访问data/props,但 DOM 未生成($el不存在);beforeMount:模板编译完成,即将挂载到 DOM,$el已存在但未插入页面;mounted:DOM 挂载完成,可操作 DOM,依赖收集在此阶段完成;beforeUpdate:数据更新后,DOM 重新渲染前,可获取更新前的 DOM;updated:DOM 重新渲染后,避免在此阶段修改数据(可能导致无限循环);beforeDestroy:实例销毁前,可清理定时器、解绑事件等;destroyed:实例销毁后,所有响应式监听和事件监听被移除。
6. Vue 组件通信方式有哪些?底层原理是什么?
常见方式及原理:
-
props / $emit:
- 父传子:通过
props传递数据,底层是组件初始化时对props的属性劫持; - 子传父:子组件用
$emit触发事件,父组件通过v-on监听,底层是事件订阅-发布模式。
- 父传子:通过
-
$parent / $children:直接访问父/子组件实例,底层是组件实例的层级引用。
-
provide / inject:祖先组件通过
provide提供数据,后代组件用inject注入,底层是依赖注入(DI)机制,避免 props 多层传递。 -
Vuex / Pinia:全局状态管理,底层是通过一个单例对象存储状态,结合响应式原理实现跨组件共享。
7. Vue 中的 computed 和 watch 有什么区别?底层原理?
computed:
- 特性:缓存性(依赖不变则不重新计算)、默认是 getter 函数,可定义 setter;
- 原理:内部是一个 惰性Watcher,依赖收集时标记为“脏值”,只有在被访问且依赖变化时才重新计算。
watch:
- 特性:监听数据变化并执行回调,支持异步操作、深度监听(
deep: true)、立即执行(immediate: true); - 原理:内部是一个 用户Watcher,数据变化时直接触发回调,无缓存。
使用场景:computed 适合依赖多个数据计算一个值;watch 适合数据变化时执行异步/复杂逻辑。
8. Vue 中的 nextTick 原理是什么?
作用:在下次 DOM 更新循环结束后执行回调,用于获取更新后的 DOM。
原理:
- 利用 JavaScript 的 事件循环机制,优先使用微任务(
Promise.then/MutationObserver),降级使用宏任务(setTimeout); - Vue 内部更新 DOM 时会将所有更新操作放入队列,等待同步代码执行完后,通过 nextTick 统一触发 DOM 更新,避免频繁重绘。
9. Vue 3 相比 Vue 2 有哪些核心改进?
- 响应式系统:用
Proxy替代Object.defineProperty,支持更多数据类型和动态属性; - Composition API:基于函数的 API(
setup/ref/reactive等),解决 Options API 中逻辑复用的问题; - 编译优化:
- 静态节点提升(
hoistStatic):将静态节点编译为常量,避免重复创建; - 补丁标记(
patchFlags):标记动态节点,diff 时只对比有变化的部分;
- 静态节点提升(
- 更好的 TypeScript 支持:原生支持 TS,类型推断更完善。
这些问题覆盖了 Vue 核心原理的关键点,理解底层实现(如响应式、diff、生命周期)能更好地应对面试中的扩展问题。
更多推荐



所有评论(0)