defineproperty&proxy

66 阅读4分钟

<span class="ne-text">Object.defineProperty</span><span class="ne-text">Proxy</span> 都是 JavaScript 中用来定义和拦截对象操作的机制,但它们的工作原理和适用场景有所不同,尤其在 拦截范围灵活性 上。以下是两者的对比,帮助你理解为什么 <span class="ne-text">Object.defineProperty</span> 只能拦截单个属性的访问和修改,而 <span class="ne-text">Proxy</span> 能够拦截整个对象的所有操作。

1. <span class="ne-text">Object.defineProperty</span>** 的局限性**

<span class="ne-text">Object.defineProperty</span> 是在 ES5 引入的,它的主要作用是 为对象的特定属性定义属性描述符 **,通过 **<span class="ne-text">getter</span><span class="ne-text">setter</span> 让你拦截单个属性的读取和修改。它对单个属性的控制非常精细,但对于整个对象,它并不能起到全局拦截的作用。

为什么只能拦截单个属性?

<span class="ne-text">Object.defineProperty</span> 是基于 属性描述符 的方式来进行操作的。在一个对象上调用 <span class="ne-text">Object.defineProperty</span> 时,**你只能为单个属性定义 <span class="ne-text">getter</span> 和 **<span class="ne-text">setter</span>,并且每次只能操作一个属性。

  • <span class="ne-text">Object.defineProperty</span> ** 的工作方式**
const obj = {};

Object.defineProperty(obj, 'name', {
  get() {
    console.log('Accessing name');
    return 'Alice';
  },
  set(value) {
    console.log(`Setting name to ${value}`);
  }
});

console.log(obj.name); // Accessing name -> Alice
obj.name = 'Bob';      // Setting name to Bob
  • **通过 **<span class="ne-text">Object.defineProperty</span> 为单个属性定义一个“描述符”,这个描述符包含了 <span class="ne-text">get</span><span class="ne-text">set</span> 函数。
  • 你可以通过这种方式对特定的属性进行控制。
  • **但是这个方法是 ** 针对属性本身的 ,并没有提供对整个对象操作的控制。
  • 局限性
const obj = { name: 'Alice' };

// 只对 `name` 属性有效
Object.defineProperty(obj, 'name', {
  get() { return 'Alice'; },
  set(value) { console.log(`Setting name to ${value}`); }
});

obj.name = 'Bob';  // 可以拦截 name
obj.age = 30;      // 无法拦截新增的属性 `age`
  • <span class="ne-text">Object.defineProperty</span> 必须为每个属性单独定义 <span class="ne-text">getter</span><span class="ne-text">setter</span>,因此如果你想为对象的多个属性设置拦截,需要为每个属性都调用一次 <span class="ne-text">Object.defineProperty</span>,而且它不能动态地拦截新增的属性。
  • 如果你在对象中添加新的属性,<span class="ne-text">Object.defineProperty</span> 不会自动为它们创建代理(你需要显式地为每个新属性调用 <span class="ne-text">Object.defineProperty</span>)。

2. <span class="ne-text">Proxy</span>** 的优势**

<span class="ne-text">Proxy</span> 是 ES6 中引入的,它提供了 全面的拦截能力 **,可以让你拦截对象的所有操作,包括属性访问、赋值、删除、方法调用等。与 **<span class="ne-text">Object.defineProperty</span> 只能拦截单个属性不同,<span class="ne-text">Proxy</span> 允许你对整个对象进行拦截,并且能够自动处理嵌套的对象。

为什么 <span class="ne-text">Proxy</span> 能够拦截整个对象?

<span class="ne-text">Proxy</span> 是基于 代理模式 实现的,它让你通过指定一个 处理器(handler) 来拦截整个对象的操作。与 <span class="ne-text">Object.defineProperty</span> 只能针对单个属性不同,<span class="ne-text">Proxy</span> 通过创建一个代理对象,拦截对该对象的所有操作。

  • <span class="ne-text">Proxy</span> ** 的工作方式**
const obj = { name: 'Alice' };

const proxy = new Proxy(obj, {
  get(target, prop) {
    console.log(`Getting ${prop}`);
    return prop in target ? target[prop] : `Property ${prop} does not exist`;
  },
  set(target, prop, value) {
    console.log(`Setting ${prop} to ${value}`);
    target[prop] = value;
    return true;
  },
  deleteProperty(target, prop) {
    console.log(`Deleting ${prop}`);
    delete target[prop];
    return true;
  }
});

console.log(proxy.name); // Getting name -> Alice
proxy.age = 30;          // Setting age to 30
delete proxy.name;      // Deleting name
  • <span class="ne-text">Proxy</span> 代理的是整个对象,而不是单独的属性。你通过定义代理的 陷阱函数(trap functions) 来拦截对象的操作。
  • <span class="ne-text">Proxy</span> 提供了很多不同的陷阱函数,例如 <span class="ne-text">get</span><span class="ne-text">set</span><span class="ne-text">deleteProperty</span><span class="ne-text">has</span> 等,用于拦截各种操作。
  • 代理是动态的,可以拦截任何对代理对象的访问,包括读取、设置、删除、方法调用等。
  • 优势
const obj = { name: 'Alice' };

const proxy = new Proxy(obj, {
  get(target, prop) {
    return prop in target ? target[prop] : `Property ${prop} not found`;
  }
});

proxy.name = 'Bob';     // 设置已有属性
proxy.age = 30;         // 设置新增属性

console.log(proxy.name); // Bob
console.log(proxy.age);  // 30
  • <span class="ne-text">Proxy</span> 通过 陷阱函数 提供了对对象所有操作的控制, 可以对整个对象进行拦截 ,而不仅仅是单一的属性。
  • **你可以动态地创建 **<span class="ne-text">Proxy</span>,而不需要显式为每个属性添加描述符。只要某个属性被访问,代理对象就会自动触发对应的陷阱函数。
  • <span class="ne-text">Proxy</span> 支持拦截新增的属性。对于对象中的所有属性(无论是已有的还是动态新增的),<span class="ne-text">Proxy</span> 都可以进行拦截。

3. 对比总结

特性<span class="ne-text">Object.defineProperty</span><span class="ne-text">Proxy</span>
拦截范围只能拦截单个属性的访问和修改可以拦截整个对象的所有操作
动态性需要显式为每个属性设置 <span class="ne-text">getter</span><span class="ne-text">setter</span>自动拦截对象上的任何操作
是否支持新增属性不支持自动拦截新增属性支持拦截新增的属性
是否可以拦截删除操作不能直接拦截删除操作可以通过 <span class="ne-text">deleteProperty</span>陷阱函数拦截
使用场景当需要精确控制单个属性时需要全面拦截对象的所有操作时
性能性能较高,适合简单的属性控制性能较低,尤其是在对象嵌套较深时

4. 总结

  • <span class="ne-text">Object.defineProperty</span> 是基于属性描述符实现的,它只能精确控制单个属性,且只能在定义时指定 <span class="ne-text">getter</span><span class="ne-text">setter</span>,并且不能动态代理新属性。
  • <span class="ne-text">Proxy</span> 则是更为灵活和强大的机制,它能拦截对象的所有操作(不仅仅是 <span class="ne-text">get</span><span class="ne-text">set</span>,还包括 <span class="ne-text">delete</span><span class="ne-text">has</span> 等)。<span class="ne-text">Proxy</span> 允许动态地对整个对象进行代理,适用于需要更高灵活性和控制的场景。

**通过 **<span class="ne-text">Proxy</span>,你可以实现对整个对象的全面拦截,而 <span class="ne-text">Object.defineProperty</span> 适合于精确控制对象的某个属性。