Неожиданное поведение ловушки Set в прокси-сервере ES6

let ar = []; 
let p = new Proxy(new Map(), { 

get: (o, k) => {
 ar.push(1)
  return Reflect.get(o, k).bind(o) 
},

set: (o, k, v) => {
 ar.push(2)
  return Reflect.set(o, k, v)
}
});
p.set(1, 2)
p.get(1)
console.log(ar) //Outputs [1,1]

Я пытаюсь перехватить операции set и get для объекта Map. Я никоим образом не пытаюсь расширить/подклассировать карту. В процессе проксирования указанного объекта Map я столкнулся с этим странным неожиданным поведением: ловушка set не срабатывает в приведенном выше коде, но вместо этого ловушка get срабатывает дважды!

Далее я продолжил регистрировать значения k(key) из ловушки get следующим образом;

//same code above
get: (o, k) => {
 console.log(k) //Logs set and then get!
  return Reflect.get(o, k).bind(o) 
}
//same code below

Я ожидаю, что массив будет [2,1] и console.log(k) в ловушке get, чтобы фактически вывести значение ключа.

Я хочу знать, почему это происходит именно так, я столкнулся с парой подобных проблем, связанных с проксированием карт, ни одна из них не привела к каким-либо разумным рассуждениям о том, почему это происходит.

Моя конечная цель — запустить событие в установленной ловушке. Использую ли я прокси для чего-то, для чего он предназначен? Если нет, то какой подход мне следует выбрать? Должен ли я отказаться от использования карты в литерал объекта, даже если это принесет все минусы его использования? пример: нет свойства длины, свойства только для строки, нет принудительно-уникальных ключей и т. д.

ОБНОВЛЕНИЕ: чем больше я копаюсь в этой проксированной карте, тем больше проблем у меня возникает. Теперь мне кажется, что ES6 Proxy API обрабатывает Карты так же, как и обычный объект. Ответ Вилл и мои раскопки подтверждают это. Почему я говорю это? Прочтите следующее;

//same code above
 get: (o, k) => {
     if(k === 'tray') return ']'
      return Reflect.get(o, k).bind(o) 
    }
    //same code below
p.tray //returns ] 

Приведенный выше код теоретически не должен работать, верно? Как будто Proxy использует ту же логику для перехвата операций с объектами при перехвате Maps! В виде;

///--While the following---//
let m = new Map();
m.set('tray', ']')
m.tray //undefined

В ответе Вила говорится, что прокси-сервер идентифицирует Map.prototype.set как первое чтение set и затем вызывает его как функцию. Разве это не означает, что ловушка set, которую я написал в исходном коде (в самом верху), не перехватывает модификацию/установку свойства карты, и фактически вместо этого используется неявный/собственный для Map-Map.prototype.set из Reflect.set, которые мы предоставляем через прокси?

Разве все это не подтверждает тот факт, что прокси и карты плохо сочетаются друг с другом? Я иду по неправильному пути? Что я неправильно понимаю, если это так? Должны ли прокси относиться к картам как к любому другому объекту?


person CoodleNoodle    schedule 18.11.2017    source источник


Ответы (1)


Это не баг, это фича (шутка).

Вы должны понимать, что именно делают .get и .set прокси. Get перехватит любую попытку чтения объекта. Возьмем ваш пример:

p.set(1,2)
p.get(1)

В первой строке мы: читаем из свойства объекта с именем set, а затем пытаемся вызвать его как функцию

Во второй строке мы читаем из свойства объекта с именем get, а затем пытаемся вызвать его как функцию.

Если вы попробуете этот код:

p.val = 5;

затем вы попытаетесь установить 5 для цели с именем val, и сеттер сработает.

Вот как работают сеттер и геттер прокси.

Итак, чтобы добиться желаемого поведения, проверьте имя свойства и верните функцию с некоторой дополнительной реализацией. И не забудьте вызвать оригинальную функцию.

Что-то вроде этого:

get: (o, k) => {
 if (k==='get') return Reflect.get(o, k).bind(o);
if (k==='set') return function(v){Reflect.set(o, k, v)}
}

Надеюсь это поможет.

Обновлять:

let obj = {
  take: (v) => v
};
let handler = {
  get: (target, key) => {
    if (key === 'take') {
      return function(v) {
        console.log(`logger says: function ${key} was called with argument: ${v}`);
      }

      return target[key];
    }
  }
};

let proxy = new Proxy(obj, handler);

proxy.take(5);
proxy.take(3);

person Drag13    schedule 18.11.2017
comment
Означает ли это косвенно, что Proxy API не предназначен для Карт, или говорит о том, что установленная ловушка не имеет смысла, когда Карты проксируются? Потому что, честно говоря, это выглядит немного некрасиво для меня. - person CoodleNoodle; 18.11.2017
comment
Нет, это не так. Возьмем этот объект: {take: (v)=› v}; Как мы можем проксировать take? Создать метод take на прокси-сервере? Нет. Мы проксируем его с помощью proxy's get. Я думаю, что одни и те же названия (set/set) вас смущают. - person Drag13; 18.11.2017
comment
Спасибо за обновление, но это не ответ на мой вопрос. Позвольте мне переформулировать вопрос, который я задал о цели установки ловушки; Есть ли какая-либо цель в set trap, когда _Map_ проксируется? Насколько я понимаю, согласно MDN, set trap должен перехватывать любые операции, которые изменяют целевой объект и эта строка -> if (k==='set') return function(v){Reflect.set(o, k, v)} создает впечатление, что когда дело доходит до проксированных карт, установленная ловушка не имеет никакой цели, поскольку даже перехват Map.prototype.set, который изменяет целевой объект, выполняется через ловушку get. - person CoodleNoodle; 18.11.2017
comment
Я думаю, что правильным ответом будет: это зависит от реализации, и следует избегать повторения этого. - person Drag13; 18.11.2017