javascript - using Proxy trap when instantiating an object - javascript

I would like to have a javascript function that I can instantiate, and catch every undefined method that is being called to it (Proxy Trap).
What I have so far is this:
var MyProxyFunction = new Proxy(function () {
console.log(">>>>>>>>>>>>>>>>>>>> constructor");
}, {
get: function(target, prop) {
if (target[prop] === undefined) {
return function() {
console.log('an otherwise undefined function!!');
};
}
else {
return target[prop];
}
}
});
Now, if I call MyProxyFunction.foo(), it will get called (I will see the "constructor" firing off and the log from the get function).
But what I would like to do is have this object instantiated (and do some initialization in the constructor) like this:
var myObj = new MyProxyFunction();
myObj.foo();
But when I do that than I get that foo() is not a function. Why? and how can I make it work when instantiating the proxy?

The explanation for that behaviour is that your constructor is proxied, but not the objects it constructs. So when you write new MyProxyFunction the proxied constructor is called, but the constructor creates a new object which has nothing to do with Proxy, but with the prototype property of the constructor.
There are several ways to make it work.
1. Apply the proxy on the prototype object
function MyProxyFunction() {
console.log(">>>>>>>>>>>>>>>>>>>> constructor");
};
MyProxyFunction.prototype = new Proxy(MyProxyFunction.prototype, {
get: function(target, prop) {
if (target[prop] === undefined) {
return function() {
console.log('an otherwise undefined function!!');
};
}
else {
return target[prop];
}
}
});
var myObj = new MyProxyFunction();
myObj.foo();
console.log('constructor:', myObj.constructor.name);
console.log('Is instance of MyProxyFunction?: ', myObj instanceof MyProxyFunction);
It now looks a bit strange to use the name MyProxyFunction, as it is not the function (the constructor) itself that is proxied.
2. Create a proxy for each instance
If you want to have a constructor that creates a new proxy each time you instantiate an object with it, then don't assign new Proxy directly to MyProxyFunction, but make it a plain constructor function that returns a new Proxy.
The object you then have to proxy is this.
function MyProxyFunction() {
console.log(">>>>>>>>>>>>>>>>>>>> constructor");
return new Proxy(this, {
get: function(target, prop) {
if (target[prop] === undefined) {
return function() {
console.log('an otherwise undefined function!!');
};
}
else {
return target[prop];
}
}
});
}
var myObj = new MyProxyFunction();
myObj.foo();
console.log('constructor:', myObj.constructor.name);
console.log('Is instance of MyProxyFunction?: ', myObj instanceof MyProxyFunction);

Related

ES6 Proxy calling methods as properties

I have the following class that is utilizing a Proxy for getting properties and methods:
class User extends Model {
static table = 'users';
_attributes = {
id: 1,
firstName: 'Testing',
lastName: 'Test'
};
constructor() {
return new Proxy(this, {
get: function(target, name) {
// proxy getting code for functions and properties
}
});
}
client() {
return this.hasOne(Client, 'clientID');
}
}
Within the get method of the proxy, retrieving attributes is trivial. I just check for their existence within _attributes, and return the value, otherwise null.
if (target._attributes.hasOwnProperty(propertyName)) {
return target._attributes[name];
}
return null;
Then I can use it as:
const user = new User();
console.log(user.id); // returns 1
console.log(user.firstName); // returns "Testing"
Within the get method of proxy, I can also check if the property called was a function, and return the appropriate function as a result:
if (typeof target[name] === 'function') {
const originalFunction = target[name];
return function(...args) {
return originalFunction.apply(this, args);
};
}
Then I can use it as:
const user = new User();
user.client(); // Returns the return value of client() from the class
However, within the get function of the proxy, I am unable to differentiate between user.client() and user.client. In the first case, I want to return the result of the function. In the second case, I want to retrieve the result of the function, perform an additional step, and then return that.
In Pseudo-code:
if (property is a function call) {
return property();
}
else if (property is a function, but not a function call) {
return property().doSomethingElse();
}
Using a Proxy, can I tell the difference between user.client() and user.client from within the get method?
In PHP, this is possible using the magic methods __get vs __call, but I am looking for a way to do this in Javascript.

Proxy aplly for method dont't call

I want to have getter for initialize method of object
var phas = new Proxy({b:9,
cont:0,
statistic:function(){
console.log(this.cont)
this.cont++
}
}, {
has: function (target, key) {
if (key == "a") return false;
return true;
},
apply: function () {
console.log('run call ')
}
}
)
phas.run();
Uncaught TypeError: phas.run is not a function
manual for proxy object https://www.xul.fr/javascript/proxy.php
You seem to have misunderstood how proxies work.
When you create a proxy, you create a proxy on that object. The proxy will not automatically extend itself to the object's properties.
The apply trap is only applicable on functions, if you proxied a function, and then called it, it would work as you expect.
If you want to create methods dynamically, you will need to do something like this instead:
var p = new Proxy({}, {
get: function(target, prop) {
// If the property exists, just return it
if (prop in target)
return target[prop];
// Otherwise, return a function
return function() { console.log("Some method", prop); };
}
});
p.run() //Logs: Some method run
typeof p.a == 'function' // true

Is it possible to change a Proxy's target?

I have a class that implements the XMLHttpRequest interface. Depending on the URL passed to open(), I can determine whether to use the default XMLHttpRequest or my custom implementation. My idea is to use a proxy to do this:
let xhr = new XHRProxy();
xhr.open('GET', 'http://blah'); // Decide here depending on URL
I did some tests using the ES6 Proxy, which seems promising, but unfortunately the proxy target cannot be modified after constructing the Proxy:
var foo = {
name() {
return "foo";
}
};
var bar = {
name() {
return "bar";
}
}
var handler = {
get(target, property, receiver) {
if (property === "switchToBar") {
// FIXME: This doesn't work because a Proxy's target is not exposed AFAIK
receiver.target = bar;
return function() {};
} else {
return target[property];
}
}
}
var proxy = new Proxy(foo, handler);
console.log(proxy.name()); // foo
proxy.switchToBar();
console.log(proxy.name()); // foo :(
I think I can accomplish what I want by not setting a target at all - instead defining all traps to delegate to the desired object - but I'm hoping for a simpler solution.
Here's a go at "defining all traps to delegate to desired object"
(function () {
let mutableTarget;
let mutableHandler;
function setTarget(target) {
if (!(target instanceof Object)) {
throw new Error(`Target "${target}" is not an object`);
}
mutableTarget = target;
}
function setHandler(handler) {
Object.keys(handler).forEach(key => {
const value = handler[key];
if (typeof value !== 'function') {
throw new Error(`Trap "${key}: ${value}" is not a function`);
}
if (!Reflect[key]) {
throw new Error(`Trap "${key}: ${value}" is not a valid trap`);
}
});
mutableHandler = handler;
}
function mutableProxyFactory() {
setTarget(() => {});
setHandler(Reflect);
// Dynamically forward all the traps to the associated methods on the mutable handler
const handler = new Proxy({}, {
get(target, property) {
return (...args) => mutableHandler[property].apply(null, [mutableTarget, ...args.slice(1)]);
}
});
return {
setTarget,
setHandler,
getTarget() {
return mutableTarget;
},
getHandler() {
return mutableHandler;
},
proxy: new Proxy(mutableTarget, handler)
};
}
window.mutableProxyFactory = mutableProxyFactory;
})();
const {
proxy,
setTarget
} = mutableProxyFactory();
setTarget(() => 0);
console.log(`returns: ${proxy()}`);
setTarget({ val: 1 });
console.log(`val is: ${proxy.val}`);
setTarget({ val: 2 });
console.log(`val is: ${proxy.val}`);
setTarget(() => 3);
console.log(`returns: ${proxy()}`);
I feel like there must be some reason this isn't supported out of the box, but I don't have enough information to comment on that further.
After hacking on this for a while, I've observed a few things. It seems the original target that the proxy constructor is called with is treated as part of the proxy's identity regardless. Setting the original target to a plain object and swapping the target to a function later raises an error, when the proxy is called. There are definitely some rough edges to this, so use with caution.
I took John's answer and improved it (I hope):
const mutableProxyFactory = (mutableTarget, mutableHandler = Reflect) => ({
setTarget(target) {
new Proxy(target, {}); // test target validity
mutableTarget = target;
},
setHandler(handler) {
new Proxy({}, handler); // test handler validity
Object.keys(handler).forEach(key => {
const value = handler[key];
if (Reflect[key] && typeof value !== 'function') {
throw new Error(`Trap "${key}: ${value}" is not a function`);
}
});
mutableHandler = handler;
},
getTarget() {
return mutableTarget;
},
getHandler() {
return mutableHandler;
},
proxy: new Proxy(
mutableTarget,
new Proxy({}, {
// Dynamically forward all the traps to the associated methods on the mutable handler
get(target, property) {
return (_target, ...args) => mutableHandler[property].apply(mutableHandler, [mutableTarget, ...args]);
}
}),
)
});
Some important differences:
John's version only has one mutableTarget/Handler variable shared by all results from mutableProxyFactory, so you probably can't call it multiple times.
The handler is allowed to have additional keys, because there's no reason it shouldn't.
Traps in the handler have this bound to the handler, as in a normal Proxy.
The user can give initial values for the handler and target. A value for the target is actually required.
mutableProxyFactory isn't assigned globally to window.
EDIT: here's a similar version but implemented as a TypeScript class. Haven't tested this one.
class MutableProxy<T extends object> {
constructor(private _target: T, private _handler: ProxyHandler<T>) {
this.target = _target;
this.handler = _handler;
}
public get target() {
return this._target;
}
public set target(target: T) {
new Proxy(target, {}); // test target validity
this._target = target;
}
public get handler() {
return this._handler;
}
public set handler(handler: ProxyHandler<T>) {
new Proxy({}, handler); // test handler validity
for (const [key, value] of Object.entries(handler)) {
if (Reflect.has(Reflect, key) && typeof value !== 'function') {
throw new Error(`Trap "${key}: ${value}" is not a function`);
}
}
this._handler = handler;
}
public get proxy() {
return new Proxy(
this.target,
new Proxy({}, {
// Dynamically forward all the traps to the associated methods on the mutable handler
get(target, property) {
return (_target: T, ...args: any[]) => {
const {handler} = this;
return handler[property].apply(handler, [this.target, ...args]);
};
}
}),
);
}
}
Is it possible to change a Proxy's target?
No, this is not possible. The proxy handler is a quite generic interface already, and by defining all traps to forward the operation to a different handler this is easily achievable. That's why there is no extra method to change the target, the interface is kept minimal. By not making the target changeable, also the shape of the proxy is preserved (e.g. whether it's callable or an array).
How about this? Instead of making foo or bar the target directly, we use a box for our target, and put foo or bar into the box.
var foo = {
name() {
return "foo";
}
};
var bar = {
name() {
return "bar";
}
};
var handler = {
get(target, property, receiver) {
if (property === "switchToBar") {
target.content = bar;
return function() {};
} else {
return target.content[property];
}
}
};
var box = {content: foo};
var proxy = new Proxy(box, handler);
console.log(proxy.name()); // foo
// Switch over to bar by calling the function
proxy.switchToBar();
// Or, we could do the switch from out here
box.content = bar;
// Either way, we get the same result
console.log(proxy.name()); // bar
In this case, our box is an object with property content. But alternatively, you could use an array with an item at index 0.

Using Proxy.apply() on Node.js does not work. Is this a bug or am I doing it wrong?

I am using Proxy to Proxy an object. The getter and setter work fine like expected. However, the apply method is never called.
var p = new Proxy({}, {
/* getter */
get(target, name) {
return target[name]
},
/* setter */
set(target, name, value) {
target[name] = value
},
/* supposedly called apply */
apply(target,that,arg) {
console.log('apply is called, what to do here?')
}
})
This way, I can assign something to p or return something even if it doesn't exist.
When I for instance let the getter function return this
get(target, name) {
return 'getting ' + name
},
and then console.log(p.flappy) I will get the response "getting flappy" even when it doesn't exist.
So far so good but when I try to call flappy doing p.flapppy() it wil throw an error that flappy is not a function.
This is still somewhat obvious because the getter does not return a function. When I let the getter return a function like this
get(target, name) {
return function() { return 'this is '+name }
},
I can call the property without it having to exist.
console.log(
p.flappy() // this is flappy!
)
So when does apply get called? Not in the snippet I just showed here and also not in this case:
p.foo = function() {
console.log('yay!')
return 'foo!'
}
It does not work to do p.foo() or p.foo.call() or p.foo.apply(), in neither cases apply is called.
The ultimate purpose of this journey is that I want to return a DIFFERENT value depending on whether a property is being read or being called. Like this:
p.someNewProperty // return what the getter function returns
p.anotherProperty() // return something else here because this is a function call
Is this possible?
I know this is question is a year old, but I ran into this as well and I found a way to do what you are trying to do. So this is for future reference, as I didn't find correct solutions elsewhere.
Short version: accessing functions inside an object (or a class) is essentially getting the property of the object that has the function. The trick is to return another Proxy with apply so you can proxy these functions correctly.
Consider the following object:
const myObject = {
a: 'Hello world!',
b: x => x * x
};
Accessing a or b shall both be caught by a Proxy's get, because they are properties of the object. You should catch all get and then filter for functions. Once you have a function, you return a new Proxy that catches this particular function with Proxy.apply.
Then, to let the function execute as intended, inside the Proxy.apply we return a Reflect.apply, which calls the original function with the correct arguments as expected.
You will end up with this code:
const myProxyObject = new Proxy(myObject, {
get(target, propKey, receiver) {
// Calling functions
if (typeof target[propKey] === 'function') {
return new Proxy(target[propKey], {
apply(applyTarget, thisArg, args) {
console.log(`Calling ${thisArg.constructor.name}.${propKey}(${args})`);
return Reflect.apply(applyTarget, thisArg, args);
}
});
}
// Accessing properties
if (target.hasOwnProperty(propKey)) {
console.log(`Get value of ${target.constructor.name}.${propKey}`);
console.log(`Value: ${target[propKey]}`);
}
return target[propKey];
}
});
Demo on jsfiddle
You don't get the result of the function, because that would require you to execute it.
Note: it is possible to use this with classes and it works very nicely. The only caveat is that your Proxy will be catching all internal functions as well. In order to prevent logging dozens of valueOfs, I highly recommend to test if a function is native or not with something like this isNative function
As documented on MDN, the apply proxy method is for proxying a function call on the proxy object itself, not a call on a method of the object.
It only works with functions (as the Proxy target), not regular object instances, but here is how it would work:
var p = new Proxy(function() {}, {
apply: function() {
console.log('apply called');
}
});
p();
The ultimate purpose of this journey is that I want to return a DIFFERENT value depending on whether a property is being read or being called.
It is not possible to directly do what you intend, nor would it really make sense. To call is to read the property.
and after some years...
yes, you can! you can return a DIFFERENT value depending on whether a property is being read or being called!
const authUser = { id: 1 }
const user = new Proxy(function () {}, {
get (target, property) {
return authUser.id
},
apply (target, thisArg, args) {
// some stuff to get user
return { id: args[0] }
}
})
console.log(user.id)
console.log(user(2).id)
or you can use two step proxy.
const authUser = { id: 1 }
const user = new Proxy(function () {}, {
get (target, property) {
return userProxy(authUser.id, property)
},
apply (target, thisArg, args) {
return userProxy(args[0])
}
})
function userProxy(id, property) {
// some stuff to get user
const user = { id }
return property ? user[property] : new Proxy(user, {
get (target, property) {
return user[property]
}
})
}
console.log(user.id)
console.log(user(2).id)

Can I throw a warning when an invalid (by my rules) value is assigned to a member of an object?

Chromium (V8?) threw a warning when I assigned the wrong value to a member of a built in object:
This has got to be the most unexpectedly helpful and clear warning that I have ever seen. Compare that to, say, the invariant violation warnings in React that have no context or meaningful line number. Or, to my own console.warn calls that happen in validation functions far away from where the user of the module actually messed up.
Is there a way that a class of my own could throw an error / warning when an invalid value is assigned to a member, that will be shown right next to the assignment, like in the picture?
Yes, you can define your properties with accessor methods:
var obj = {
get foo() {
return this._foo;
},
set foo(value) {
if (/*...value is invalid...*/) {
throw new Error(/*...*/);
}
this._foo = value;
}
};
When used, they look just like normal properties:
obj.foo = "some value";
var x = obj.foo;
More here: specification | MDN
Of course, with the above, a determined person could just assign to obj._foo instead. If you're concerned about that, you can define the accessors within an IIFE and use a local variable for the backing value:
var obj = (function() {
var foo;
return {
get foo() {
return foo;
},
set foo(value) {
if (/*...value is invalid...*/) {
throw new Error(/*...*/);
}
foo = value;
}
};
})();
That accessor method syntax is just one way to define accessors; you can also do it on an existing object via Object.defineProperty (spec | MDN) and Object.defineProperties (spec | MDN) (or while creating a new object with Object.create [spec | MDN] by specifying the second argument).
defineProperty example:
var obj = {};
(function() {
var foo;
Object.defineProperty(obj, "foo", {
get: function() {
return foo;
},
set: function(value) {
if (/*...value is invalid...*/) {
throw new Error(/*...*/);
}
foo = value;
}
});
})();
In ES2015, you can also do this when using class; it looks a lot like initializer syntax, but without commas between the methods:
class Thingy {
get foo() {
return this._foo;
}
set foo(value) {
if (/*...value is invalid...*/) {
throw new Error(/*...*/);
}
this._foo = value;
}
}
To avoid the obj._foo problem, you could use a WeakMap (spec | MDN) to store the values keyed by this:
let Thingy = (() => {
let foos = new WeakMap();
class Thingy {
get foo() {
return foos.get(this);
}
set foo(value) {
if (/*...value is invalid...*/) {
throw new Error(/*...*/);
}
foos.set(this, value);
}
}
})();
There I've used a foo-specific map, but you could also use a map keyed by this to store all your backing properties as an object.

Categories