Vue 2 vs Vue 3 全生命周期执行流程图解
一、核心要点速览
💡 核心考点
- 初始化阶段: Vue 2 通过
new Vue(),Vue 3 通过createApp()。 - 响应式系统: Vue 2 使用
Object.defineProperty递归遍历;Vue 3 使用Proxy代理,支持懒初始化。 - 依赖收集: Vue 2 使用
Dep和Watcher;Vue 3 使用track函数和targetMap(WeakMap)。 - 更新触发: Vue 2 使用
notify()派发;Vue 3 使用trigger函数。 - 生命周期: Vue 2 的 Options API vs Vue 3 的 Composition API (
setup)。
二、Vue 2 vs Vue 3 全流程对比图解
理解 Vue 的执行过程,需要从“初始化 -> 响应式建立 -> 挂载 -> 依赖收集 -> 触发更新”这几个关键阶段进行对比。
三、深度对比:执行流程的差异
1. 响应式初始化 (Reactive Initialization)
| 阶段 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy) |
|---|---|---|
| 原理 | 递归遍历 data 选项,将属性转换为 getter/setter。 | 使用 Proxy 直接代理整个对象。 |
| 时机 | 在 beforeCreate 和 created 之间完成。 | 在 setup() 执行过程中,调用 reactive/ref 时。 |
| 性能 | 递归导致大对象初始化慢,且无法监听新增/删除属性。 | 懒初始化:只有访问嵌套对象时才进行下层代理,性能更好。 |
2. 依赖收集 (Dependency Collection)
Vue 2:
- 每个属性都有一个
Dep对象。 - Dep.target: 是一个全局静态属性,指向当前正在进行渲染或计算的
Watcher实例。 - 渲染时触发
getter,将当前的Watcher(观察者) 添加到Dep的订阅列表中。
javascript/** * Vue 2 响应式核心三剑客:Observer, Dep, Watcher */ // 1. Dep: 依赖收集器 let uid = 0; class Dep { constructor() { this.id = uid++; this.subs = []; // 存储所有的 Watcher } // 添加订阅者 addSub(sub) { this.subs.push(sub); } // 依赖收集:将当前 Watcher 加入 Dep depend() { if (Dep.target) { Dep.target.addDep(this); } } // 通知更新 notify() { const subs = this.subs.slice(); for (let i = 0, l = subs.length; i < l; i++) { subs[i].update(); } } } // 全局静态变量,同一时间只有一个 Watcher 在工作 Dep.target = null; const targetStack = []; function pushTarget(target) { targetStack.push(target); Dep.target = target; } function popTarget() { targetStack.pop(); Dep.target = targetStack[targetStack.length - 1]; } // 2. Watcher: 订阅者/观察者 class Watcher { constructor(vm, expOrFn, cb) { this.vm = vm; this.cb = cb; this.getter = typeof expOrFn === 'function' ? expOrFn : () => vm[expOrFn]; this.value = this.get(); // 实例化时立即触发一次 get } get() { pushTarget(this); // 1. 将自己设为全局目标 let value; try { value = this.getter.call(this.vm); // 2. 执行渲染/求值,触发属性 getter } finally { popTarget(); // 3. 结束后移出 } return value; } addDep(dep) { dep.addSub(this); } update() { this.run(); } run() { const value = this.get(); // 重新求值并收集依赖 if (value !== this.value) { const oldValue = this.value; this.value = value; this.cb.call(this.vm, value, oldValue); // 执行回调 } } } // 3. Observer: 数据劫持者 class Observer { constructor(value) { this.value = value; this.walk(value); } // 遍历对象属性 walk(obj) { const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } } } function defineReactive(obj, key, val) { const dep = new Dep(); // 每个属性闭包中都有一个唯一的 Dep if (arguments.length === 2) { val = obj[key]; } // 递归观察子对象 observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { console.log(`[Getter] 访问属性: ${key}`); if (Dep.target) { dep.depend(); // 收集依赖 } return val; }, set(newVal) { if (val === newVal) return; console.log(`[Setter] 修改属性: ${key} -> ${newVal}`); val = newVal; observe(newVal); // 新值也需要观察 dep.notify(); // 通知更新 } }); } function observe(value) { if (typeof value !== 'object' || value === null) return; return new Observer(value); } // --- 演示用法 --- const data = { name: 'Vue', info: { version: '2.x' } }; observe(data); // 模拟一个 Watcher(比如组件渲染) new Watcher(data, () => { return `Current Name: ${data.name}, Version: ${data.info.version}`; }, (val) => { console.log(`[Update] 界面更新了: ${val}`); }); // 修改数据,触发更新 data.name = 'Vue 2.7'; data.info.version = '2.7.14';Watcher 执行流程详解
- 初始化: 实例化
Watcher时,构造函数会调用this.get()。 - 设置全局目标:
pushTarget(this)将当前 Watcher 实例赋值给全局变量Dep.target。 - 触发收集: 执行
getter函数(如渲染函数)。在此期间,所有被访问到的响应式属性都会触发其Object.defineProperty.get。 - 建立连接: 属性的
getter调用dep.depend(),将Dep.target(即当前 Watcher) 添加到该属性私有的Dep订阅列表中。 - 清理目标:
popTarget()将Dep.target恢复为之前的状态,防止非预期情况下的依赖收集。 - 派发更新: 当数据被修改时,触发
setter调用dep.notify(),遍历subs列表并调用每个 Watcher 的update方法,最终触发重新渲染或回调。
- 每个属性都有一个
Vue 3:
- 使用全局的
targetMap(一个 WeakMap) 来管理依赖。 - 触发
get时调用track(),建立target -> key -> effect的多级映射关系。
javascript// Vue 3 依赖收集简化实现 const targetMap = new WeakMap(); let activeEffect = null; function track(target, key) { if (activeEffect) { let depsMap = targetMap.get(target); if (!depsMap) targetMap.set(target, (depsMap = new Map())); let dep = depsMap.get(key); if (!dep) depsMap.set(key, (dep = new Set())); dep.add(activeEffect); } } function reactive(target) { return new Proxy(target, { get(target, key, receiver) { const res = Reflect.get(target, key, receiver); track(target, key); return res; } }); }- 使用全局的
3. 生命周期与 setup 的关系
Vue 3 引入了 setup(),它改变了生命周期的执行顺序:
- setup(): 在
beforeCreate之前执行。 - 生命周期钩子: 所有的 Composition API 钩子 (如
onMounted) 都必须在setup期间同步注册。 - 命名变化:
beforeDestroy->onBeforeUnmountdestroyed->onUnmounted
四、高频面试题
1. 为什么 Vue 2 无法监听数组下标的变化,而 Vue 3 可以?
回答:
- Vue 2:
Object.defineProperty只能监听属性的读写,无法监听数组长度变化或索引赋值。Vue 2 通过重写数组原型方法(如push,splice)来间接解决。 - Vue 3:
Proxy代理的是整个对象(包括数组),它可以原生拦截到数组的索引操作和length的变动,因此不需要重写数组方法。
2. 简述 Vue 2 的 Dep、Watcher 和 Observer 的关系。
回答:
- Observer: 负责将对象转为响应式。
- Dep: 依赖收集器,每个属性一个,负责存储所有的订阅者(Watcher)。
- Watcher: 订阅者,当数据变化时,Dep 会通知 Watcher,Watcher 负责触发更新(如重新渲染)。
3. Vue 3 的 targetMap 为什么使用 WeakMap?
回答: 因为 targetMap 的键是组件或数据对象。使用 WeakMap 可以确保当对象被销毁(垃圾回收)时,其对应的依赖关系也会被自动清理,从而防止内存泄漏。
4. Vue 3 触发更新 (trigger) 的过程是怎样的?
回答:
- 当响应式数据被修改,触发
Proxy.set。 - 执行
trigger()函数。 - 从
targetMap中根据target和key找到对应的effects集合。 - 将这些
effects放入调度队列(Scheduler)中异步执行。
5. 生命周期钩子在 Vue 3 中是如何实现的?
回答: Vue 3 的生命周期钩子是通过 injectHook 函数将回调函数注册到当前的组件实例 (Internal Component Instance) 上的。由于 setup 执行时 Vue 会全局记录当前活跃的实例,因此这些钩子能够正确地与实例绑定。
五、总结一句话
- Vue 2: 是基于属性拦截的被动通知体系(每个属性都是一个消息中心)。
- Vue 3: 是基于整体代理的主动映射体系(全局维护一个精密的依赖地图)。