【问题标题】:JavaScript Proxy target object conflicts with property descriptorsJavaScript 代理目标对象与属性描述符冲突
【发布时间】:2019-12-10 20:35:54
【问题描述】:

我正在创建一个虚拟化对象,该对象使用规则引擎延迟构建其属性,作为一种跳过计算从未读取的值的技术。为此,我使用了Proxy。代理似乎为访问转发和虚拟化提供双重职责。就我而言,我主要关心的是后者,而不是前者。

我遇到的问题与尝试在代理中实现 getOwnPropertyDescriptor 陷阱有关。这样做时我收到错误:

TypeError:代理上的“getOwnPropertyDescriptor”:陷阱返回的属性“foo”的描述符与代理目标中的现有属性不兼容

因为我实际上并没有将请求转发到包装对象,所以我一直使用Object.freeze({}) 作为传递给new Proxy(...) 的第一个参数。

function createLazyRuleEvaluator(rulesEngine, settings) {
  const dummyObj = Object.freeze({});
  Object.preventExtensions(dummyObj);

  const valueCache = new Map();

  const handlers = {
    get(baseValue, prop, target) {
      if (valueCache.has(prop)) 
        return valueCache.get(prop);

      const value = rulesEngine.resolveValue(settings, prop);
      valueCache.set(prop, value);
      return value;
    },

    getOwnPropertyDescriptor(baseValue, prop) {
      return !propIsInSettings(prop, settings) ? undefined : {
        configurable: false,
        enumerable: true,
        get() {
          return handlers.get(baseValue, prop, null);
        }
      };
    },
  };

  return new Proxy(dummyObj, handlers);
}

// This throws
Object.getOwnPropertyDescriptor(createLazyRuleEvaluator(/*...*/), 'foo');

代理对象旨在类似于具有只读属性的对象,该属性无法扩展或具有任何其他此类幻想。我尝试使用冻结且不可扩展的对象,但我仍然被告知属性描述符不兼容。

当我尝试使用非冻结对象作为代理目标时,我得到一个不同的类型错误:

TypeError:代理上的“getOwnPropertyDescriptor”:陷阱报告属性“foo”的不可配置性,该属性在代理目标中不存在或可配置

我在这里做错了什么?有什么方法可以让我的代理显示不可配置性?

【问题讨论】:

  • "我实际上并没有将请求转发到一个包装的对象" - 是的。您没有为其编写陷阱处理程序的所有请求仍会转发到对象,例如has 或set。
  • 不,我不是。发布的代码不完整(我正在使用 TDD 并遇到了这个问题)。我将实现所有的陷阱处理程序。

标签: javascript es6-proxy


【解决方案1】:

您可能想查阅invariants 的handler.getOwnPropertyDescriptor() 列表,不幸的是其中包括这些:

  • 如果属性不作为目标对象的自有属性存在且目标对象不可扩展,则无法将其报告为存在。
  • 如果属性不作为目标对象的自有属性存在或作为目标对象的可配置自有属性存在,则不能将其报告为不可配置。

由于您的target 是一个冻结的空对象,因此您的handler.getOwnPropertyDescriptor() 陷阱的唯一有效返回值是undefined。


但要解决根本问题,请在尝试访问属性描述符时延迟初始化它们:

function createLazyRuleEvaluator(rulesEngine, settings) {
  const dummyObj = Object.create(null);
  const valueCache = new Map();
  const handlers = {
    get(target, prop) {
      if (valueCache.has(prop)) 
        return valueCache.get(prop);

      const value = prop + '_value'; // rulesEngine.resolveValue(settings, prop)
      valueCache.set(prop, value);
      return value;
    },
    getOwnPropertyDescriptor(target, prop) {
      const descriptor =
        Reflect.getOwnPropertyDescriptor(target, prop) ||
        { value: handlers.get(target, prop) };

      Object.defineProperty(target, prop, descriptor);
      return descriptor;
    },
  };

  return new Proxy(dummyObj, handlers);
}

console.log(Object.getOwnPropertyDescriptor(createLazyRuleEvaluator(/*...*/), 'foo'));

您可以对每个方法陷阱遵循此模式,通过延迟初始化任何访问的属性来抢占对您的dummyObj 的不需要的突变。

【讨论】:

  • 那么这是否意味着不可能创建一个呈现为不可扩展的完全虚拟代理?
  • @Jacob 这不一定是真的。给我几分钟来尝试一个想法,我会更新我的答案。 before 从getOwnPropertyDescriptor() 返回之前,可能会延迟定义目标对象上的属性,以便在技术上不违反不变量。
  • @Jacob 在我这样做之前,你能用handler.get() 的示例实现来更新你的问题吗?我的编辑在这种情况下会更有帮助。
  • 当然,完成(希望足以解决这个问题)。
  • @Jacob 这看起来可行吗? Object.create(null) 不是绝对必要的,它可以与 {} 一样工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-26
  • 2012-10-02
  • 1970-01-01
  • 1970-01-01
  • 2019-01-26
  • 1970-01-01
  • 2012-06-25
相关资源
最近更新 更多