Целевой объект JavaScript Proxy конфликтует с дескрипторами свойств

Я создаю виртуализированный объект, который лениво создает свои свойства с помощью механизма правил, чтобы пропустить вычисление значений, которые никогда не считываются. Для этого я использую файл 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», которое либо не существует, либо настраивается в цели прокси

Что я здесь делаю неправильно? Можно ли каким-либо образом сделать так, чтобы мой прокси не настраивался?


person Jacob    schedule 10.12.2019    source источник
comment
На самом деле я не перенаправляю запросы на обернутый объект — да, вы это делаете. Все запросы, для которых вы не написали обработчик прерываний, все равно перенаправляются на объект, например has или set.   -  person Bergi    schedule 11.12.2019
comment
Нет я не. Опубликованный код не завершен (я использую TDD и попал в эту загвоздку). Я буду реализовывать все обработчики ловушек.   -  person Jacob    schedule 11.12.2019


Ответы (1)


Возможно, вы захотите ознакомиться со списком инварианты для 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, лениво инициализируя любые доступные свойства.

person Patrick Roberts    schedule 10.12.2019
comment
Означает ли это, что невозможно создать полностью виртуальный прокси-сервер, который представляется нерасширяемым? - person Jacob; 10.12.2019
comment
@ Джейкоб, это не обязательно правда. Дайте мне несколько минут, чтобы опробовать идею, и я обновлю свой ответ. Можно было бы лениво определять свойства целевого объекта до возврата из getOwnPropertyDescriptor(), чтобы технически не нарушались инварианты. - person Patrick Roberts; 10.12.2019
comment
@Jacob, прежде чем я это сделаю, не могли бы вы обновить свой вопрос примером реализации handler.get()? Мое редактирование будет гораздо полезнее в этом контексте. - person Patrick Roberts; 11.12.2019
comment
Конечно, сделано (надеюсь, достаточно, чтобы понять это). - person Jacob; 11.12.2019
comment
@ Джейкоб, это кажется выполнимым? Object.create(null) не является строго необходимым, он может работать и с {}. - person Patrick Roberts; 11.12.2019
comment
@Jacob Я вижу твою правку. Может быть, это не очевидно, но главный ключ к моему ответу — это вызов Object.defineProperty(target, prop, descriptor); перед returning из handlers.getOwnPropertyDescriptor() - person Patrick Roberts; 11.12.2019
comment
Похоже, эта техника может сработать. Собираюсь поиграть с ним. - person Jacob; 11.12.2019
comment
Превосходно! Кажется странным, что нужен этот обходной путь, но он определенно работает. - person Jacob; 11.12.2019
comment
@Джейкоб, это не странно. Инварианты накладываются таким образом, чтобы объект мог предположительно существовать с указанным наблюдаемым поведением. Таким образом, Proxy не может вести себя непоследовательно. - person Patrick Roberts; 11.12.2019
comment
Я имею в виду, что это странно, если вы ожидаете, что Proxy можно использовать для создания виртуального объекта с нуля; все эти ограничения, связанные с целевым объектом, делают это действительно хакерским, когда вы на самом деле не хотите, чтобы эта вещь имела какое-либо отношение к цели. Другой пример странности сейчас заключается в том, что я не могу реализовать isExtensible так, чтобы он возвращал false. - person Jacob; 11.12.2019
comment
Виртуализация редко бывает тривиальной, но вы усложняете себе задачу, требуя от прокси-сервера обработки всех методов отражения в дополнение к обычному доступу. - person Patrick Roberts; 11.12.2019
comment
Если бы я пропустил методы отражения, это упростило бы задачу, но тогда методы отражения вернули бы неверные результаты. Я хочу убедиться, что этот объект выглядит максимально точно во время отражения. - person Jacob; 11.12.2019