12

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

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

代码示例 1 - defineProperty

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捕获属性变化?
  • 为什么要添加setoverride defineProperty
  • 如何让代理正确捕获defineProperty新属性和set属性更改?
4

1 回答 1

10

为什么defineProperty捕获属性变化?

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

为什么添加 set 会覆盖 defineProperty?

因为当您添加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.

于 2020-06-13T10:07:14.373 回答