Skip to content

Vue 2 vs Vue 3 全生命周期执行流程图解

一、核心要点速览

💡 核心考点

  • 初始化阶段: Vue 2 通过 new Vue(),Vue 3 通过 createApp()
  • 响应式系统: Vue 2 使用 Object.defineProperty 递归遍历;Vue 3 使用 Proxy 代理,支持懒初始化。
  • 依赖收集: Vue 2 使用 DepWatcher;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 的执行过程,需要从“初始化 -> 响应式建立 -> 挂载 -> 依赖收集 -> 触发更新”这几个关键阶段进行对比。

Vue 2 vs Vue 3 全流程执行对比图解


三、深度对比:执行流程的差异

1. 响应式初始化 (Reactive Initialization)

阶段Vue 2 (Object.defineProperty)Vue 3 (Proxy)
原理递归遍历 data 选项,将属性转换为 getter/setter使用 Proxy 直接代理整个对象。
时机beforeCreatecreated 之间完成。setup() 执行过程中,调用 reactive/ref 时。
性能递归导致大对象初始化慢,且无法监听新增/删除属性。懒初始化:只有访问嵌套对象时才进行下层代理,性能更好。

2. 依赖收集 (Dependency Collection)

  • Vue 2:

    • 每个属性都有一个 Dep 对象。
    • Dep.target: 是一个全局静态属性,指向当前正在进行渲染或计算的 Watcher 实例。
    • 渲染时触发 getter,将当前的 Watcher (观察者) 添加到 Dep 的订阅列表中。

    Vue 2 依赖收集流程

    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 执行流程详解

    1. 初始化: 实例化 Watcher 时,构造函数会调用 this.get()
    2. 设置全局目标: pushTarget(this) 将当前 Watcher 实例赋值给全局变量 Dep.target
    3. 触发收集: 执行 getter 函数(如渲染函数)。在此期间,所有被访问到的响应式属性都会触发其 Object.defineProperty.get
    4. 建立连接: 属性的 getter 调用 dep.depend(),将 Dep.target (即当前 Watcher) 添加到该属性私有的 Dep 订阅列表中。
    5. 清理目标: popTarget()Dep.target 恢复为之前的状态,防止非预期情况下的依赖收集。
    6. 派发更新: 当数据被修改时,触发 setter 调用 dep.notify(),遍历 subs 列表并调用每个 Watcher 的 update 方法,最终触发重新渲染或回调。
  • Vue 3:

    • 使用全局的 targetMap (一个 WeakMap) 来管理依赖。
    • 触发 get 时调用 track(),建立 target -> key -> effect 的多级映射关系。

    Vue 3 依赖收集流程

    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(),它改变了生命周期的执行顺序:

  1. setup(): 在 beforeCreate 之前执行。
  2. 生命周期钩子: 所有的 Composition API 钩子 (如 onMounted) 都必须在 setup 期间同步注册。
  3. 命名变化:
    • beforeDestroy -> onBeforeUnmount
    • destroyed -> onUnmounted

四、高频面试题

1. 为什么 Vue 2 无法监听数组下标的变化,而 Vue 3 可以?

回答:

  • Vue 2: Object.defineProperty 只能监听属性的读写,无法监听数组长度变化或索引赋值。Vue 2 通过重写数组原型方法(如 push, splice)来间接解决。
  • Vue 3: Proxy 代理的是整个对象(包括数组),它可以原生拦截到数组的索引操作和 length 的变动,因此不需要重写数组方法。

2. 简述 Vue 2 的 Dep、Watcher 和 Observer 的关系。

回答:

  1. Observer: 负责将对象转为响应式。
  2. Dep: 依赖收集器,每个属性一个,负责存储所有的订阅者(Watcher)。
  3. Watcher: 订阅者,当数据变化时,Dep 会通知 Watcher,Watcher 负责触发更新(如重新渲染)。

3. Vue 3 的 targetMap 为什么使用 WeakMap?

回答: 因为 targetMap 的键是组件或数据对象。使用 WeakMap 可以确保当对象被销毁(垃圾回收)时,其对应的依赖关系也会被自动清理,从而防止内存泄漏

4. Vue 3 触发更新 (trigger) 的过程是怎样的?

回答:

  1. 当响应式数据被修改,触发 Proxy.set
  2. 执行 trigger() 函数。
  3. targetMap 中根据 targetkey 找到对应的 effects 集合。
  4. 将这些 effects 放入调度队列(Scheduler)中异步执行。

5. 生命周期钩子在 Vue 3 中是如何实现的?

回答: Vue 3 的生命周期钩子是通过 injectHook 函数将回调函数注册到当前的组件实例 (Internal Component Instance) 上的。由于 setup 执行时 Vue 会全局记录当前活跃的实例,因此这些钩子能够正确地与实例绑定。


五、总结一句话

  • Vue 2: 是基于属性拦截的被动通知体系(每个属性都是一个消息中心)。
  • Vue 3: 是基于整体代理的主动映射体系(全局维护一个精密的依赖地图)。
最近更新