I have the following contrived code:
class Animal {
get age() {
return this.baseage + 10;
}
age2() {
return this.baseage + 10;
}
}
const handler = {
"get": function(target, key) {
if (key === "baseage") {
return 20;
}
return target[key];
}
};
const animal = new Proxy(new Animal(), handler);
console.log(animal.age);
console.log(animal.age2());
Which produces
NaN
30
On node 6.11.0.
I would expect the code in the class getter, specifically this.baseage, to go through the proxy's handler too, but that does not seem to be the case. Is there any reason for it?
return target[key]; is not the same behavior as the default get handler. This is the cause of the broken get age function.
const handler = {
"get": function(target, key) {
if (key === "baseage") {
return 20;
}
return target[key];
}
};
should be
const handler = {
"get": function(target, key, receiver) {
if (key === "baseage") {
return 20;
}
return Reflect.get(target, key, receiver);
}
};
When you do target[key] you are calling the get age(){, but you are calling it with target as this, which is the new Animal object, not the proxy. Since the Proxy object is the one that handles baseage, not the Animal, you get back undefined.
In this example, receiver is the actual proxy object, so you could potentially do receiver[key] to have your snippet work, but there are tons more edge cases that you'd still not be handling in a general way.
Every single Proxy handler function has a Reflect.XX version that exposes the default behavior. Whenever you're writing a proxy and just want it to act like it normally would, you should be using Reflect.
Related
I have the following proxy handler that logs get events.
const proxyHandler = {
get: function(target, prop) {
console.log("get", prop);
return Reflect.get(target, prop);
}
};
const obj = new Proxy(
{
value: 4,
text: "hi",
get textVal() {
return this.text.repeat(this.value);
},
getTextVal() {
return this.text.repeat(this.value);
}
},
proxyHandler
);
console.log("------- normal func -------")
console.log(obj.getTextVal());
console.log("------- getter func -------")
console.log(obj.textVal);
When I log console.log(obj.getTextVal()) I get:
get getTextVal
get text
get value
hihihihi
But when I log the getter console.log(obj.textVal), I only get the following:
get textVal
hihihihi
How can I make obj.textVal log the get text and get get value events using proxy? ie. When running console.log(obj.textVal) I would like the following result.
get getTextVal
get text
get value
hihihihi
The above answer works but there is a bit more elegant solution. You are missing the receiver in your Proxy trap and Reflect arguments. Simply change the Proxy to this:
const proxyHandler = {
get: function(target, prop, receiver) {
console.log("get", prop);
return Reflect.get(target, prop, receiver);
}
};
Notice the new receiver in the trap and Reflect arguments.
There is an important distinction between a Proxy trap target and receiver. In this case, the target is the underlying raw object while the receiver is the Proxy wrapper. If you do not pass the receiver to the Reflect call everything inside the get operation will be run against the raw object and won't trigger the Proxy traps.
If you have the time I suggest you read the relevant parts of the ES6 spec to fully grasp the difference between these two. Otherwise, just make sure that you forward all Proxy trap args to the matching Reflect call if you are aiming for a transparent wrap.
You can set the Proxy instance to proxyHandler object and access the properties through it (instead of this).
const proxyHandler = {
get: function(target, prop) {
console.log("get", prop);
return Reflect.get(target, prop);
}
};
const proxifiedObj = {
value: 4,
text: "hi",
get textVal() {
return this.proxyInstance.text.repeat(this.proxyInstance.value);
},
getTextVal() {
return this.text.repeat(this.value);
}
}
obj = proxifiedObj.proxyInstance = new Proxy(proxifiedObj, proxyHandler);
console.log("------- normal func -------")
console.log(obj.getTextVal());
console.log("------- getter func -------")
console.log(obj.textVal);
console.log(obj.textVal);
get getTextVal
get text
get value
hihihihi
Update:
Or you could do the same thing by creating a custom Proxy that does the assignment for you
(Note: Proxy class cannot be extended but we can use the constructor return value pattern):
class InstanceAwareProxy {
constructor(proxifiedObject, proxyHandler) {
return proxifiedObject.proxyInstance
= new Proxy(proxifiedObject, proxyHandler);
}
}
obj = new InstanceAwareProxy(proxifiedObj, proxyHandler);
I want to check that my object properties and method or anything else is called or not? for example,
// functions
function app(){
return {
name : 'Md Tahazzot',
info : function(){
return this.name;
}
};
}
Now if I call this like app(), I mean In this case I am not called any of the object properties or methods. So, Is it possible to check this that I am called only the function nothing else like this app().name ?
You could return a Proxy. If the proxy's getters (or setters?) are ever called, then you know that something has been done other than simply call the function - something attempted to get or set a property on the returned object:
function app() {
const target = {
name: 'Md Tahazzot',
info: function() {
return this.name;
}
};
return new Proxy(target, {
get(target, prop) {
console.log('Get attempted');
return target[prop];
},
set(target, prop, newVal) {
console.log('Set attempted');
return target[prop] = newVal;
}
});
}
console.log('Creating "a":');
const a = app();
console.log('Creating "b":');
const b = app();
b.name;
console.log('Creating "c":');
const c = app();
c.foo = 'foo';
console.log(c.foo);
If you have to do this from outside the app, then apply the same logic after the object has been returned:
function app() {
return {
name: 'Md Tahazzot',
info: function() {
return this.name;
}
};
}
const obj = new Proxy(app, {
get(target, prop) {
console.log('Get attempted');
return target[prop];
},
set(target, prop, newVal) {
console.log('Set attempted');
return target[prop] = newVal;
}
});
console.log('Proxy created');
obj.name;
As functions are nothing but objects in JavaScript, you can create property on function itself to store any info at function level.
You could do something like this:
function app(){
app.callsCount = app.callsCount || 0;
app.callsCount++;
return {
name : 'Md Tahazzot',
info : function(){
return this.name;
}
};
}
And can be used like this:
app().name
app.callsCount // 1
app()
app.callsCount //2
Keep in mind, once function is called, the count is increased, if you want to increase count on inner function call you could do that too. However it would not be straight forward to know if a property is called after app function call.
Not exactly sure what exactly you are trying to achieve.
I have the following code that is using Proxy for Class setter. In my example I am tracking specific variable to update some other variables. My Setter is writing a log of all changes to Console. However if I try to modify a variable from a setter itself, variable gas modified, but the Proxy isn't called. Is it by design to avoid looping? Or am I missing something?
class darthVader {
constructor() {
return new Proxy(this, {
set(obj, prop, value) {
console.log(`Setting ${prop} to ${value}`)
obj[prop] = value
return true
}
})
}
set resistance(val) {
this._resistance= val
this.darkSide = false
}
get resistance() { return this._R2D2 }
}
let newHero = new darthVader()
newHero.resistance = 11
console.log(newHero.darkSide)
The problem is that your trap just runs obj[prop] = value, which sets a property on the target obj not on the proxy. What you should do instead is to use the Reflect.set method that provides the default implementation for the set trap, and expects an optional receiver argument. This receiver is the object that setters will be evaluated against, and you should pass the receiver argument of the set trap (which will refer to the newHero proxy that you assigned resistance to).
class DarthVader {
set resistance(val) {
this._resistance= val
this.darkSide = false
}
get resistance() { return this._R2D2 }
}
let newHero = new Proxy(new DarthVader, {
set(target, prop, value, receiver) {
console.log(`Setting ${prop} to ${value}`)
return Reflect.set(target, prop, value, receiver)
// ^^^^^^^^^^^
// obj[prop] = value
}
});
newHero.resistance = 11
console.log(newHero.darkSide)
The obj inside the set method refers to what this is when you do return new Proxy(this, and that object is not a proxy, but the darthVader instance itself - the one that's in the process of being created by the darthVader constructor. So, when you assign to a property of obj, you're putting a property directly on the darthVader instance, rather than on the proxy instance (which is the newHero). So, the proxy method doesn't get called.
If you wanted to recursively invoke the proxy, you could define it (let's say, as the variable name proxy) before returning it from the constructor, and then reference proxy inside the set method, but given the current logic, this results in a stack overflow because you'd be continually calling the proxy's setter:
class darthVader {
constructor() {
const proxy = new Proxy(this, {
set(obj, prop, value) {
console.log(`Setting ${prop} to ${value}`)
proxy[prop] = value
return true
}
})
return proxy;
}
set resistance(val) {
this._resistance = val
this.darkSide = false
}
get resistance() {
return this._R2D2
}
}
let newHero = new darthVader()
newHero.resistance = 11
console.log(newHero.darkSide)
TL:DR; Is it possible to make a property of object to be invocable ( as a function ) only ?
What i mean by this
class Foo{
bar(value){
return value
}
}
let newFoo = new Foo()
console.log(newFoo.bar(123)) // should work fine as function is invoked
console.log(newFoo.bar) // here i need to throw or display an error instead of returning value
I tried to do this with Proxy and handler.get trap, but i have no clue how to capture whether it is a function call or just property access,
class Foo {
bar(value) {
return value
}
}
const proxied = new Proxy(new Foo(), {
get: function(target, prop, reciver) {
if (prop === 'bar') {
throw new Error('Bar is method need to be invoced')
}
return target[prop]
}
})
console.log(proxied.bar(true))
console.log(proxied.bar)
I have also checked handler.apply but this also doesn't seems to be of no use as this is a trap on function, not on property
class Foo {
bar(value) {
return value
}
}
const proxied = new Proxy(new Foo(), {
apply: function(target, thisArg, argumentsList) {
return target(argumentsList[0])
},
get: function(target, prop, reciver) {
if (prop === 'bar') {
throw new Error('Bar is method need to be invoced')
}
return target[prop]
}
})
console.log(proxied.bar(true))
console.log(proxied.bar)
No, this is not possible. There is no distinction between
const newFoo = new Foo()
newFoo.bar(123);
and
const newFoo = new Foo()
const bar = newFoo.bar;
Function.prototype.call.call(bar, newFoo, 123); // like `bar.call(newFoo, 123)`
// or Reflect.apply(bar, newFoo, [123]);
i.e. neither newFoo nor bar can distinguish these "from the inside". Now arbitrary things could happen in between the property access and the method call, and during the property access you cannot know what will happen next, so you cannot throw an exception prematurely. The method call might happen never (in newFoo.bar;), and there's no way to recognise that from newFoo alone.
The only approach would be to intercept all other accesses on newFoo and its properties, and throw after you detected a mischievous sequence; possibly having your "linter" check the sequence from the outside after the whole program ran:
const lint = {
access: 0,
call: 0,
check() {
console.log(this.access == this.call
? "It's ok"
: this.access > this.call
? "method was not called"
: "property was reused");
},
run(fn) {
this.call = this.access = 0;
try {
fn();
} finally {
this.check();
}
}
}
function bar(value) {
lint.call++; lint.check();
return value;
}
class Foo {
get bar() {
lint.check(); lint.access++;
return bar;
}
}
lint.run(() => {
const newFoo = new Foo;
newFoo.bar(123);
});
lint.run(() => {
const newFoo = new Foo;
newFoo.bar;
});
lint.run(() => {
const newFoo = new Foo;
const bar = newFoo.bar;
bar(123);
bar(456);
});
The better solution would probably to write your own interpreter for simple expressions, which would only allow method calls.
I am trying to call .toString() on a function proxy.
Simply creating a function proxy and calling toString causes "TypeError: Function.prototype.toString is not generic", setting the toString to return the source of the original causes "RangeError: Maximum call stack size exceeded", but creating a get trap for toString works.
Why does simply setting the toString function not work, but making a get trap does?
function wrap(source) {
return(new Proxy(source, {}))
}
wrap(function() { }).toString()
function wrap(source) {
let proxy = new Proxy(source, {})
proxy.toString = function() {
return(source.toString())
}
return(proxy)
}
wrap(function() { }).toString()
function wrap(source) {
return(new Proxy(source, {
get(target, key) {
if(key == "toString") {
return(function() {
return(source.toString())
})
} else {
return(Reflect.get(source, key))
} } })) }
wrap(function() { }).toString()
I was having the same issue. I finally figured out that it's an issue with this. Add a get trap to your handler, bind the proxied object as this on the proxied property if it's a function, and it seems to work okay:
function wrap(source) {
return new Proxy(source, {
get: function (target, name) {
const property = target[name];
return (typeof property === 'function')
? property.bind(target)
: property;
}
});
}
console.log(wrap(function () {}).toString());
TypeError: Function.prototype.toString is not generic
Seems like Function.prototype.toString is not supposed to be called on Proxy.
proxy.toString = function() {
This assignment to proxy is passed to the source object as you do not have trap for assignment. If you check source.hasOwnProperty('toString') you'll get true. When you add get trap, you don't change toString method and don't add it into source object, so it works.
The other possible solution is
function wrap(source) {
let proxy = new Proxy(source, {})
proxy.toString = Function.prototype.toString.bind(source)
return proxy
}