JavaScript - 代理集与 defineProperty

JavaScript - Proxy set vs. defineProperty

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

代码示例 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 - 观察

代码示例 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 - 观察

问题

Why does defineProperty catch property changes?

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

Why does the addition of set override defineProperty?

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

How do I get the proxy to correctly trap defineProperty for new properties and set for property changes?

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.