defineProperty 和 Proxy

134 阅读2分钟

作用

  • 都可以在JS中对对象属性的拦截和代理

区别

  1. 使用语法和方式

    • Object.defineProperty
      • ES5引入的方法
      • 对单个属性进行代理
      • 直接在对象上定义一个新属性,或修改对象现有属性,并返回该对象
      const obj = {};
      Object.defineProperty(obj, 'property', {
          value: 1,
          writable: true,
          enumerable: true,
          configurable: true
      });
      console.log(obj.property); // 输出: 1
      
    • Proxy
      • ES6新特性
      • 对整个对象进行代理
      • 创建一个对象的代理
      const target = {};
      const handler = {
          get: function(target, property) {
              return `Getting property: ${property}`;
          }
      };
      const proxy = new Proxy(target, handler);
      console.log(proxy.name); // 输出: Getting property: name
      
  2. 拦截范围

    • Object.defineProperty
      • 只能拦截对象属性的 读取(get)设置(set) 操作,无法直接拦截其他操作(如删除属性、in 操作符、遍历属性等)。
      • 对于数组的索引修改或方法调用(如 pushpop)也无法直接监听,需额外处理。
      • 监听对象属性的变化,需要对每个属性单独使用 Object.defineProperty 进行定义
      const obj = {};
      let value = 0;
      Object.defineProperty(obj, 'count', {
          get: function() {
              return value;
          },
          set: function(newValue) {
              value = newValue;
              console.log('Count updated:', value);
          },
      });
      obj.count = 1; // 输出: Count updated: 1
      
    • Proxy
      • 提供了 13 种拦截操作(如 getsetdeletePropertyhasownKeys 等),可拦截更广泛的操作,包括属性删除、in 操作符、遍历属性、数组索引修改等
      • 无需为每个属性单独设置
      const target = { name: 'John' };
      const handler = {
          get: function(target, property) {
              return `Intercepted get: ${target[property]}`;
          },
          set: function(target, property, value) {
              target[property] = value;
              console.log(`Intercepted set: ${property} = ${value}`);
              return true;
          },
          deleteProperty: (target, property) => {
              console.log('deleteProperty', target, property)
          },
      };
      const proxy = new Proxy(target, handler);
      console.log(proxy.name); // 输出: Intercepted get: John
      proxy.age = 30; // 输出: Intercepted set: age = 30
      delete proxy.name; // 输出 deleteProperty {name: 'John', age: 30} name
      
  3. 对新增属性的处理

    • Object.defineProperty

      • 需要预先为每个属性定义 getter/setter无法自动监听新增属性(需手动调用 Vue.set 或重新遍历对象)。
    • Proxy

      • 直接代理整个对象自动监听所有属性变化(包括新增和删除属性),无需额外操作。
  4. 性能

    • Object.defineProperty
      • 初始化时需遍历所有属性进行劫持,性能开销随对象规模增大而增加。
      • 但属性访问时性能较好(直接调用 getter/setter)。
    • Proxy
      • 初始化时无需遍历属性,性能更高效,尤其适合大规模对象。
      • 但每次操作需通过代理层,可能略微增加运行时开销(现代引擎优化后影响较小)。
  5. 兼容性

    • Object.defineProperty

      • 支持 ES5 及以上环境(IE9+),兼容性较好。
    • Proxy

      • 需 ES6 环境(IE 不支持),兼容性较低,但现代浏览器和 Node.js 均已支持。

选择建议

  • 若需兼容旧环境或仅需简单劫持,使用 Object.defineProperty
  • 若需全面拦截、处理动态属性或复杂对象,优先选择 Proxy

总结

特性Object.definePropertyProxy
拦截操作get/set13 种拦截方法(如 deletein
新增属性监听需手动处理自动监听
数组监听需重写方法直接拦截索引变化
初始化性能需遍历属性,开销较大无需遍历,高效
嵌套对象处理递归劫持按需代理
兼容性ES5+(广泛支持)ES6+(现代环境)
适用场景简单属性劫持(如 Vue2)复杂响应式系统(如 Vue3)