JavaScript - Proxy set vs. defineProperty



我想构建一个代理来检测对象更改:

  • 定义了新属性。
  • 现有属性已更改。

代码示例 1 - 定义属性

const me = {
name: "Matt"
}
const proxy = new Proxy(me, {
defineProperty: function(target, key, descriptor) {
console.log(`Property ${key} defined.`);
return Object.defineProperty(target, key, descriptor);
}
});
proxy // { name: 'Matt' }
proxy.name = "Mark";
// Property name defined.
// Mark
proxy.age = 20;
// Property age defined.
// 20

代码示例 1 - 观察

  • proxy有一个属性name这是我所期望的。
  • 更改name属性告诉我已定义name;不是我所期望的。
  • 定义age属性告诉我age已经定义;正如我所期望的那样。

代码示例 2 - 设置

const me = {
name: "Matt"
}
const proxy = new Proxy(me, {
defineProperty: function(target, key, descriptor) {
console.log(`Property ${key} defined.`);
return Object.defineProperty(target, key, descriptor);
},
set: function(target, key, value) {
console.log(`Property ${key} changed.`);
return target[key] = value;
}
});
proxy // { name: 'Matt' }
proxy.name = "Mark";
// Property name changed.
// Mark
proxy.age = 20;
// Property age changed.
// 20

代码示例 2 - 观察

  • proxy有一个属性name这是我所期望的。
  • 更改name属性告诉我name已更改;正如我所期望的那样。
  • 定义age属性告诉我age已更改;不是我所期望的。

问题

  • 为什么defineProperty会捕捉到属性变化?
  • 为什么添加set会覆盖defineProperty
  • 如何让代理正确捕获新属性的defineProperty和属性更改的set

为什么defineProperty捕获属性更改?

因为当您更改数据属性(而不是访问器(时,通过一系列规范步骤,它最终会成为 [[DefineOwnProperty]] 操作。这就是更新数据属性的定义方式:[[Set]] 操作调用 OrdinarySet,该操作调用 OrdinarySetWithOwnDescriptor,后者调用 [[DefineOwnProperty]],从而触发陷阱。

为什么添加设置覆盖会定义属性?

因为当您添加set陷阱时,您将捕获 [[Set]] 操作并直接在目标上执行,而不是通过代理。所以defineProperty陷阱没有被触发。

如何让代理正确捕获新属性的defineProperty并为属性更改进行设置?

defineProperty陷阱需要区分何时调用它来更新属性,何时调用它来创建属性,它可以通过在目标上使用Reflect.getOwnPropertyDescriptorObject.prototype.hasOwnProperty来执行此操作。

const me = {
name: "Matt"
};
const hasOwn = Object.prototype.hasOwnProperty;
const proxy = new Proxy(me, {
defineProperty(target, key, descriptor) {
if (hasOwn.call(target, key)) {
console.log(`Property ${key} set to ${descriptor.value}`);
return Reflect.defineProperty(target, key, descriptor);
}
console.log(`Property ${key} defined.`);
return Reflect.defineProperty(target, key, descriptor);
},
set(target, key, value, receiver) {
if (!hasOwn.call(target, key)) {
// Creating a property, let `defineProperty` handle it by
// passing on the receiver, so the trap is triggered
return Reflect.set(target, key, value, receiver);
}
console.log(`Property ${key} changed to ${value}.`);
return Reflect.set(target, key, value);
}
});
proxy; // { name: 'Matt' }
proxy.name = "Mark";
// Shows: Property name changed to Mark.
proxy.age = 20;
// Shows: Property age defined.

这有点即兴发挥,但它会让你朝着正确的方向前进。

你可以只用一个set陷阱来做到这一点,但这不会被任何直接进入[[DefineOwnProperty]]而不是通过[[Set]的操作触发,例如Object.defineProperty

最新更新