2
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]

I am trying to intercept both set and get operations on a Map object. I am in no way trying to extend/subclass a Map. In the process of proxying said Map object, I came across this weird unexpected behavior, the set trap isn't being fired in the above code, but instead the get trap gets fired twice!

I further proceeded to log the k(key) values from the get trap in the following way;

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

The behavior I expect is for the array to be [2,1] and console.log(k) at the get trap to actually output the value of the key.

I wish to know why this happens as so, I've gone through a couple problems like this in here related to proxyifying maps, none of them lead to any sensible reasoning as to why this is happening.

My end goal is to fire an event at the set trap. Am I using Proxy for something it is meant to be used? If not, what approach should I take? Should I abandon using a Map to an Object Literal even though it will bring all the cons of using one? ex: no length property, string-only properties, no forced-unique keys etc.

UPDATE: The more and more I dig into this proxified Map, the more issues I keep coming across. It now seems to me as if the ES6 Proxy API treats Maps the same way as it does an ordinary object. The answer by Vill and my digging backs this up. Why do I say this? Read the following;

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

The above code shouldn't theoretically succeed, right? It is as if Proxy is using the same logic to intercept object operations when intercepting Maps as well! As;

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

Vill's answer says that the Proxy identifies Map.prototype.set as first reading set and invoking it as a function next. Doesn't that mean in the set trap I've written in the original code(on the very top) doesn't intercept the modification/setting a property of the Map and in fact the implicit/native to Map-Map.prototype.set is used instead of the Reflect.set which we grant through the Proxy?

Doesn't all this further enforce the fact that Proxy and Maps don't mix together well? Am I heading down the wrong way? What am I misunderstanding if so? Are Proxies supposed to treat Maps like just any other object?

CoodleNoodle
  • 324
  • 3
  • 17

1 Answers1

3

It is not bug it is feature (joke).

You should understand what exactly proxy's .get and .set do. Get will intercept any reading try of the object. Lets take your example:

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

On the first line we: read from object property with name set and then try to invoke it as function

On the second line we read from object property with name get and then try to invoke it as a function.

If you will try this code:

p.val = 5;

then you will try to set 5 to the target with name val and setter will fire.

This how proxy's setter and getter work.

So, to achive desired behavior, check the property name and return the function with some additional implementation. And do not forget to call original function.

Somth like this:

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

Hope this helps.

Update:

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);
Drag13
  • 5,859
  • 1
  • 18
  • 42
  • Does this indirectly say that the Proxy API isn't meant for Maps or does it say that the set trap has no purpose when Maps are proxified? Because honestly that looks a tad bit ugly to me. – CoodleNoodle Nov 18 '17 at 10:02
  • No, it doesn't. Lets take this object: {take: (v)=> v}; How can we proxifie **take**? Create **take** mehtod on the proxy? No. We will proxifie it by proxy's get. I think that the same names (set/set) confusing you. – Drag13 Nov 18 '17 at 10:14
  • Thanks for the update, but that doesn't answer my question. Let me reword the question I asked about the purpose of the set trap; **Is there any purpose in the set trap when a __Map__ is proxified?** To my understanding, per MDN, the set trap is supposed to intercept any operations that modify the target object and this line -> `if (k==='set') return function(v){Reflect.set(o, k, v)}` makes it look like when it comes to proxified Maps, the set trap doesn't have any purpose as even the interception of `Map.prototype.set` which modifies the target obj is done through the `get` trap. – CoodleNoodle Nov 18 '17 at 10:31
  • I think the correct answer will be: it depends per realisation and relaying on this should be avoided. – Drag13 Nov 18 '17 at 10:44