我想构建一个代理来检测对象更改:
- 定义了新属性。
- 现有属性已更改。
代码示例 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.getOwnPropertyDescriptor
或Object.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
。