大家都知道,从 Vue 3 开始,双向绑定机制从 Object.defineProperty 转换成了 Proxy ,但很少有人去问为什么,本着学习前端知识又可以水一篇文章的想法,于是我又开始水了一篇文章。

1. 性能和灵活性

  • Vue 2 使用 Object.defineProperty 为对象的每个属性定义 getter 和 setter,监控每个属性的变化。但对于嵌套对象,它需要递归地为每层属性进行定义,这会导致性能开销和代码复杂度的增加。因为存在深度遍历,就会存在效率的损失,
    而在深度遍历后,对对象进行属性 新增 或者使用 delete 进行删除,delete操作符不会触发set函数)就无法实现响应式。

function isObject(v) {
  return typeof v === 'object' && v != null;
}

function observe(obj) {
  if (!isObject(obj)) {
    return;
  }

  for (const k in obj) {
    let v = obj[k];
    if (isObject(v)) {
      observe(v);
    }
    Object.defineProperty(obj, k, {
      get() {
        console.log(k, '读取', v);
        return v;
      },
      set(val) {
        console.log(k, '更改', val);
        v = val;
      }
    })
  }
}

const obj = {
  name: '小明',
  age: 18,
  love: {
    food: 'apple',
    ball: 'basketball'
  }
}

observe(obj);
obj.love.food = 3;
delete obj.age; // 不会触发
  • Vue 3 使用 Proxy 可以直接代理整个对象,自动监听对象内层次嵌套的属性变化,不需要递归地遍历对象的每一层。因此,它对复杂对象的性能优化更加显著。

function isObject(v) {
  return typeof v === 'object' && v != null;
}

const obj = {
  name: '小明',
  age: 18,
  love: {
    food: 'apple',
    ball: 'basketball'
  }
}

function observe(obj) {
  const proxy = new Proxy(obj, {
    get(target, k) {
      let v = target[k];
      // 读取属性的时候,如果是属性是对象才会再进行监听。
      if(isObject(v)) {
        v = observe(v);
      }
      console.log(k, '读取')
      return v;
    },
    set(target, k, v) {
      if (target[k] !== v) {
        target[k] = v;
        console.log(k, '更改')
      }
    },
    deleteProperty(target, k) {
      console.log(k, '删除')
      delete target[k];
    }
  })
  return proxy;
}

const proxy = observe(obj);
proxy.love.ce = 10;
delete proxy.love.ce;

2. 对新增和删除属性的支持

  • Object.defineProperty 无法检测到属性的新增或删除,只能在对象定义时设置已存在的属性。Vue 2 通过 Vue.setVue.delete 提供了一种手动侦测新增属性的方式,但这种方法较为繁琐且不是完全自动的。

  • Proxy 可以直接监听对象的新增和删除操作,通过 setdeleteProperty 拦截器自动响应属性的变化。这样 Vue 3 可以更自然地支持动态属性,提升了响应式的灵活性和简便性。

3. 数组变化的检测

  • Vue 2 需要用特殊的方式来监控数组的变化(如 pushpop 等),因为 defineProperty 无法直接拦截数组的长度变化或其他方法。Vue 2 中通过重写数组方法来监控数组的变化,但这种做法复杂且影响性能。

  • Proxy 可以直接代理数组对象并捕获其各种操作,无需重写数组方法。它不仅可以侦测到数组内容变化,还可以监控数组的长度变化。这使得 Vue 3 的响应式数组更加高效和直观。

4. 更好的跨平台兼容性

  • Vue 3 的响应式机制不再依赖特定的属性定义方式,可以在更多平台(如 Web 和移动设备)上有更一致的表现。同时,Proxy 的 API 在现代 JavaScript 引擎中也有较好的支持度和优化,这使得 Vue 3 的响应式系统更具兼容性和效率。

5. 未来可扩展性

  • Proxy 能够拦截 13 种不同的操作(如 getsetdeletePropertyhasownKeys 等),Vue 3 只使用了部分功能(主要是 getset)来构建响应式数据系统。这种扩展能力使得 Vue 3 的响应式系统未来可以轻松实现更多的功能和优化,比如在调试模式下增加属性访问的日志等。

6. 总结

Vue 3 引入 Proxy 来替代 Object.defineProperty,解决了 Vue 2 在动态属性、嵌套对象、数组监控等方面的不足,同时减少了性能开销和代码复杂度。Proxy 的使用不仅让 Vue 3 的响应式系统更加高效、灵活,还为未来的功能扩展提供了更多可能性。