Why is Set not executed when using Proxy on Map Object?
If you want a proxy setup that lets you intercept attempted calls to the Map .set()
method, you'd have to do something like this:
let handler = {
get: (target, property, proxy) {
if (property === "set") {
console.log("intercepted a '.set' access on the proxied map");
return (key, value) => {
console.log("intercepted a 'set()' call on the proxied map");
return target.set(key, value);
};
}
return target[property];
}
};
The "get" handler method is called for any property access is attempted. In the method call
proxy.set("some key", "some value");
the "set" property of the object has to be looked up before the method can actually be called. It's that lookup operation that results in the handler "get" method invocation.
Now if you make the proxy as in your code and then do
proxy.set("some key", "some value");
you'll see that the log message fires when that "interceptor" function you returned is invoked.
Observe changes to a Map using a Proxy
First off, understand that your error also reproduces with just
var map = new Map();
var proxy = new Proxy(map, {});
proxy.set(1, 1);
It is not related to your usage of set(obj, prop, value)
.
Why it fails
To break that a bit more, understand that this is basically the same as doing
var map = new Map();
var proxy = new Proxy(map, {});
Map.prototype.set.call(proxy, 1, 1);
which also errors. You are calling the set
function for Map
instances, but passing it a Proxy
instead of a Map
instance.
And that is the core of the issue here. Map
s store their data using an private internal slot that is specifically associated with the map
object itself. Proxies do not behave 100% transparently. They allow you to intercept a certain set of operations on an object, and perform logic when they happen, which usually means proxying that logic through to some other object, in your case from proxy
to map
.
Unfortunately for your case, proxying access to the Map instance's private internal slot is not one of the behaviors that can be intercepted. You could kind of imagine it like
var PRIVATE = new WeakMap();
var obj = {};
PRIVATE.set(obj, "private stuff");
var proxy = new Proxy(obj, {});
PRIVATE.get(proxy) === undefined // true
PRIVATE.get(obj) === "private stuff" // true
so because the object pass as this
to Map.prototype.set
is not a real Map, it can't find the data it needs and will throw an exception.
Solution
The solution here means you actually need to make the correct this
get passed to Map.prototype.set
.
In the case of a proxy, the easiest approach would be to actually intercept the access to .set
, e.g
var map = new Map();
var proxy = new Proxy(map, {
get(target, prop, receiver) {
// Perform the normal non-proxied behavior.
var value = Reflect.get(target, prop, receiver);
// If something is accessing the property `proxy.set`, override it
// to automatically do `proxy.set.bind(map)` so that when the
// function is called `this` will be `map` instead of `proxy`.
if (prop === "set" && typeof value === "function") value = value.bind(target);
return value;
}
});
proxy.set(1, 1);
Of course that doesn't address your question about intercepting the actual calls to .set
, so for that you can expand on this to do
var map = new Map();
var proxy = new Proxy(map, {
get(target, prop, receiver) {
var value = Reflect.get(target, prop, receiver);
if (prop === "set" && typeof value === "function") {
// When `proxy.set` is accessed, return your own
// fake implementation that logs the arguments, then
// calls the original .set() behavior.
const origSet = value;
value = function(key, value) {
console.log(key, value);
return origSet.apply(map, arguments);
};
}
return value;
}
});
proxy.set(1, 1);
Why is my Proxy wrapping a Map's function calls throwing TypeError?
apply
traps calling (if you were proxying a function), not method calls (which are just property access, a call, and some this
shenanigans). You could supply get
and return a function instead:
var cache = new Proxy(new Map(), {
get(target, property, receiver) {
return function () {
console.log('hello world!');
};
}
});
I don’t suppose you’re just trying to override parts of Map
, though? You can inherit from its prototype in that case (this is a much better option than the proxy if it’s an option at all):
class Cache extends Map {
set(key, value) {
console.log('hello world!');
}
}
const cache = new Cache();
new Proxy(new Map(), {}).values
So it seems that Map
methods complain if its thisArg is not an actual Map
object. One solution would be to add a get
to the proxy that checks if the property being fetched is a function, and if so, it returns a function that calls the requested function using the original, non-Proxy object.
const things = new Proxy(new Map(), {
set(t, k, v) {
console.log(t, k, v);
Reflect.set(t, k, v);
},
get(t, k) {
if (typeof t[k] === "function") {
return (...args) => Reflect.apply(t[k], t, args)
}
return t[k];
}
});
One potential down-side to this is that the function returned will be effectively bound to the original map. Probably not an issue for most cases, but it will render calls like things.values.call(someOtherMap)
useless. There are probably ways to work around this if it's an issue.
Why is Set Incompatible with Proxy?
I am attempting to
Proxy()
aSet()
However, you haven't used any of the available traps - there is no add
one. All that you can intercept in the call p.add(55)
is the property access to .add
on the proxy, which goes through the get
trap and returns a function.
If you want to intercept calls to the add
method, you don't need a proxy at all, better (subclass and) overwrite that method similar to how .set
was overridden here and here for Map
.
proxying a
Set()
in any way breaks it categorically
Yes, because the proxy is not a Set
any more.
var s = new Set([42]);
var p = new Proxy(s, {});
s.has(42) // true
console.log(s === p) // false
p.has.call(s, 42) // true
p.has(42) // exception - calls `has` on `p`, not on `s`
Calling Set
methods on objects that are no True Sets does throw an exception (which e.g. can be used for detecting them). For your particular case, see ECMAScript 6 §23.2.3.1:
"If
S
does not have a[[SetData]]
internal slot, throw aTypeError
exception."
And indeed, p
is a proxy (which does have the internal Proxy methods and slots, especially [[ProxyHandler]]
and [[ProxyTarget]]
) instead of a set like s
with its [[SetData]]
internal slot.
You reasonably were expecting that "if a trap has not been defined, the default behavior is to forward the operation to the target", however that only applies to standard behaviours like property access, and not the internal slots of exotic objects.
Why isn't ownKeys Proxy trap working with Object.keys()?
The reason is simple: Object.keys
returns only properties with the enumerable flag. To check for it, it calls the internal method [[GetOwnProperty]]
for every property to get its descriptor. And here, as there’s no property, its descriptor is empty, no enumerable flag, so it’s skipped.
For Object.keys
to return a property, we need it to either exist in the object, with the enumerable flag, or we can intercept calls to [[GetOwnProperty]] (the trap getOwnPropertyDescriptor does it), and return a descriptor with enumerable: true.
Here’s an example of that:
let user = { };
user = new Proxy(user, {
ownKeys(target) { // called once to get a list of properties
return ['a', 'b', 'c'];
},
getOwnPropertyDescriptor(target, prop) { // called for every property
return {
enumerable: true,
configurable: true
/* ...other flags, probable "value:..." */
};
}
});
console.log( Object.keys(user) ); // ['a', 'b', 'c']
Related Topics
How to Use Nodejs to Open Default Browser and Navigate to a Specific Url
Understanding Ajax Cors and Security Considerations
When to Use the JavaScript Mime Type Application/JavaScript Instead of Text/Javascript
Three.Js:2Xmeshes Using Same Vector as Position
How to Stop a Page from Unloading (Navigating Away) in Js
JavaScript Use Variable as Object Name
JavaScript - Return String Between Square Brackets
How to Check Whether <Ng-Content> Is Empty? (In Angular 2+ Till Now)
Typeerror: _Firebase_Webpack_Imported_Module_2_.Default.Collection Is Not a Function
How to Store a JavaScript Function in JSON
Using Jquery to Find an Element at a Particular Position
How to Get Url Parameters with JavaScript
Array.Push() Makes All Elements the Same When Pushing an Object
JavaScript - Are Dom Redraw Methods Synchronous
Change Image Size with JavaScript
Displaying a Number in Indian Format Using JavaScript
Using Chrome, How to Find to Which Events Are Bound to an Element