I'm trying to create an object that is basically both a Proxy
and an EventTarget
. The goal is to be able to subscribe to any changes made to this object.
Here's how I define this object:
const target = new EventTarget()
const state = new Proxy(target, {
set: (obj, key, value) => {
Reflect.set(obj, key, value)
obj.dispatchEvent(new CustomEvent('change', {detail: {key, value}}))
},
deleteProperty: (obj, key) => {
Reflect.deleteProperty(obj, key)
obj.dispatchEvent(new CustomEvent('change', {detail: {key, value: undefined}}))
}
})
At this point I'd like to be able to call state.addEventListener('change', console.log)
but this gives me an error:
Uncaught TypeError: Illegal invocation
So here's what works:
target.addEventListener('change', console.log)
state.foo = 'bar'
// logs the event
But as I said, I'd like to have one single unified object that can be both the target (can be listened to with addEventListener
) and the store of values (proxied object in charge of dispatching the events when modified). So far, this method only works if you carry around both target
and state
...
Any idea why I can't call addEventListener
by going through the Proxy
?
Technically, calling state.addEventListener()
goes through the get
prototype method, so I tried defining get: Reflect.get
in the proxy handler but it adds nothing... (Even though it was indeed reached because I tried adding a console.log
there too)
So why can't I call addEventListener
through the proxy but it works fine directly on the target
object?
I'm trying to create an object that is basically both a Proxy
and an EventTarget
. The goal is to be able to subscribe to any changes made to this object.
Here's how I define this object:
const target = new EventTarget()
const state = new Proxy(target, {
set: (obj, key, value) => {
Reflect.set(obj, key, value)
obj.dispatchEvent(new CustomEvent('change', {detail: {key, value}}))
},
deleteProperty: (obj, key) => {
Reflect.deleteProperty(obj, key)
obj.dispatchEvent(new CustomEvent('change', {detail: {key, value: undefined}}))
}
})
At this point I'd like to be able to call state.addEventListener('change', console.log)
but this gives me an error:
Uncaught TypeError: Illegal invocation
So here's what works:
target.addEventListener('change', console.log)
state.foo = 'bar'
// logs the event
But as I said, I'd like to have one single unified object that can be both the target (can be listened to with addEventListener
) and the store of values (proxied object in charge of dispatching the events when modified). So far, this method only works if you carry around both target
and state
...
Any idea why I can't call addEventListener
by going through the Proxy
?
Technically, calling state.addEventListener()
goes through the get
prototype method, so I tried defining get: Reflect.get
in the proxy handler but it adds nothing... (Even though it was indeed reached because I tried adding a console.log
there too)
So why can't I call addEventListener
through the proxy but it works fine directly on the target
object?
2 Answers
Reset to default 4Since you are using a proxy you do need to use the getter like you thought but you more than likely didnt bind the function so it would be invoked with the correct this
. As you lose that connection when you return the function without it, it would instead refer to the proxy object which isnt itself implementing the EventTarget/EventListener methods.
let target = document.documentElement;
let targetProxy = new Proxy(target,{
get:(obj,key)=>{
let value = Reflect.get(obj,key);
if(typeof(value) == "function"){
return value.bind(obj);
}
return value;
}
});
targetProxy.addEventListener('click',()=>console.log('clicked'));
Oh, nevermind... I found the answer... Still posting it though, as it wasn't as straightforward for me as I would have hoped and it might help someone.
The issue is that when the proxy returns a function, the function doesn't have the proper scope (this
). So you need to bind the proper scope to the returned function.
This can be done by defining a getter:
get: function (obj, key) {
const result = Reflect.get(obj, key)
if(typeof result === 'function')
return result.bind(obj)
return result
}
Once this is done, it's as simple as this:
const state = new Proxy(new EventTarget(), handler)
state.addEventListener('change', console.log)
state.foo = 'bar'
// logs the event
On a W3C remendations level, I am unsure as to why a Proxy
shouldn't return functions with their proper scope...