Vue2/Vue3 响应式原理对比指南

1. 基本实现原理

1.1 Vue2 响应式实现 (Object.defineProperty)

// Vue2 响应式核心实现
function defineReactive(obj, key, val) {
  // 递归处理嵌套对象
  observe(val);
  
  const dep = new Dep();
  
  Object.defineProperty(obj, key, {
    get() {
      // 依赖收集
      if (Dep.target) {
        dep.depend();
      }
      return val;
    },
    set(newVal) {
      if (val === newVal) return;
      val = newVal;
      // 触发更新
      dep.notify();
    }
  });
}

// 遍历对象所有属性
function observe(obj) {
  if (!obj || typeof obj !== 'object') return;
  
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key]);
  });
}

1.2 Vue3 响应式实现 (Proxy)

// Vue3 响应式核心实现
function reactive(target) {
  if (!isObject(target)) return target;

  const handler = {
    get(target, key, receiver) {
      // 依赖收集
      track(target, key);
      const result = Reflect.get(target, key, receiver);
      // 深层代理
      return isObject(result) ? reactive(result) : result;
    },
    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 hadKey = hasOwn(target, key);
      const result = Reflect.deleteProperty(target, key);
      if (hadKey && result) {
        // 触发更新
        trigger(target, key);
      }
      return result;
    }
  };

  return new Proxy(target, handler);
}

2. 核心差异对比

2.1 实现机制

特性

Vue2 (Object.defineProperty)

Vue3 (Proxy)

拦截方式

属性级别拦截

对象级别拦截

初始化时机

初始化时递归遍历所有属性

访问时才进行代理(懒代理)

内存占用

需要为每个属性创建getter/setter

只需要一个代理对象

属性删除/添加

需要通过 Vue.set/Vue.delete

可以直接监听

2.2 性能对比

  1. 初始化性能:
// Vue2 - 需要递归遍历所有属性
function observe(obj) {
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key]);
    // 递归处理嵌套对象
    if (typeof obj[key] === 'object') {
      observe(obj[key]);
    }
  });
}

// Vue3 - 访问时才代理
const proxy = new Proxy(target, {
  get(target, key) {
    // 只在访问时才进行代理
    const value = Reflect.get(target, key);
    return isObject(value) ? reactive(value) : value;
  }
});
  1. 内存占用:
// Vue2 - 每个属性都需要定义getter/setter
const obj = { a: 1, b: 2, c: 3 };
// 需要创建3个getter/setter

// Vue3 - 只需要一个代理对象
const proxy = new Proxy(obj, handler);
// 只需要创建一个Proxy实例

2.3 功能特性对比

  1. 数组操作:
// Vue2 - 需要重写数组方法
const arrayMethods = ['push', 'pop', 'shift', 'unshift', 'splice'];
arrayMethods.forEach(method => {
  // 重写数组方法以触发更新
});

// Vue3 - 直接支持数组操作
const arr = reactive([1, 2, 3]);
arr.push(4); // 自动触发更新
arr[1] = 5;  // 自动触发更新
  1. 新增属性:
// Vue2
const obj = { a: 1 };
// 新增属性需要使用 Vue.set
Vue.set(obj, 'b', 2);

// Vue3
const obj = reactive({ a: 1 });
// 直接添加即可
obj.b = 2; // 自动触发更新

3. 优缺点分析

3.1 Vue2 (Object.defineProperty)

优点:

  1. 兼容性好,支持 IE8+
  2. 代码实现相对简单

缺点:

  1. 需要递归遍历对象所有属性
  2. 无法监听数组索引和长度变化
  3. 无法监听对象属性的添加和删除
  4. 需要额外的 API (Vue.set/Vue.delete)

3.2 Vue3 (Proxy)

优点:

  1. 性能更好(懒代理)
  2. 功能更强大(可以监听更多操作)
  3. 代码更简洁(不需要递归)
  4. 可以监听动态属性

缺点:

  1. 兼容性较差(不支持 IE11)
  2. 无法 polyfill

4. 为什么 Proxy 更高效?

  1. 初始化效率:
  • Object.defineProperty 需要递归遍历对象的所有属性
  • Proxy 采用懒代理,只在访问时才创建代理对象
  1. 内存占用:
  • Object.defineProperty 需要为每个属性创建 getter/setter
  • Proxy 只需要创建一个代理对象
  1. 操作拦截:
  • Object.defineProperty 只能拦截属性的读写
  • Proxy 可以拦截多达 13 种操作
  1. 数组处理:
  • Object.defineProperty 需要重写数组方法
  • Proxy 可以直接监听数组操作

5. 实际应用建议

  1. Vue2 项目:
  • 避免深层嵌套数据结构
  • 使用扁平化的数据结构
  • 合理使用 Vue.set/Vue.delete
  1. Vue3 项目:
  • 可以更自由地使用嵌套数据
  • 直接操作数组和对象
  • 利用 Proxy 的特性优化性能

6. 总结

Vue3 的响应式系统相比 Vue2 有显著改进:

  1. 性能更好
  2. 功能更强大
  3. 代码更简洁
  4. 开发体验更好

选择建议:

  1. 新项目建议使用 Vue3
  2. 需要兼容 IE11 的项目使用 Vue2
  3. 大型项目推荐 Vue3(性能优势明显)