I'm running the following script through Google Chrome Version 57.0.2987.133:
var loggingProxyHandler = {
"get" : function(targetObj, propName, receiverProxy) {
let ret = Reflect.get(targetObj, propName, receiverProxy);
console.log("get("+propName.toString()+"="+ret+")");
return ret;
},
"set" : function(targetObj, propName, propValue, receiverProxy) {
console.log("set("+propName.toString()+"="+propValue+")");
return Reflect.set(targetObj, propName, propValue, receiverProxy);
}
};
function onRunTest()
{
let m1 = new Map();
let p1 = new Proxy(m1, loggingProxyHandler);
p1.set("a", "aval"); // Exception thrown from here
}
onRunTest();
NOTE: Requires a browser supporting ES2015's Proxy
When run, I see the handler's get trap is called to return the Map's set function
and then I receive the following error:
"Uncaught TypeError: Method Map.prototype.set called on incompatible receiver [object Object]"
at Proxy.set (native)
...
I tried removing the trap functions from the loggingProxyHandler (making it an empty object) but still receive the same error.
My understanding was that a Proxy object was supposed to be able to generated for all native ES5 and ES2015 javascript objects. Array seems to work well under the same proxy handler.
Did I misunderstand the specs?
Is my code missing something?
Is there a known bug in Chrome? (I did a search and found no defects for Chrome on this subject.)
The reason you're getting the error is that the proxy isn't getting involved in the p1.set() method call (other than that the get trap is used to retrieve the function reference). So once the function reference has been retrieved, it's called with this set to the proxy p1, not the map m1 — which the set method of a Map doesn't like.
If you're really trying to intercept all property access calls on the Map, you can fix it by binding any function references you're returning from get (see the *** lines):
const loggingProxyHandler = {
get(target, name/*, receiver*/) {
let ret = Reflect.get(target, name);
console.log(`get(${name}=${ret})`);
if (typeof ret === "function") { // ***
ret = ret.bind(target); // ***
} // ***
return ret;
},
set(target, name, value/*, receiver*/) {
console.log(`set(${name}=${value})`);
return Reflect.set(target, name, value);
}
};
function onRunTest() {
const m1 = new Map();
const p1 = new Proxy(m1, loggingProxyHandler);
p1.set("a", "aval");
console.log(p1.get("a")); // "aval"
console.log(p1.size); // 1
}
onRunTest();
NOTE: Requires a browser supporting ES2015's Proxy
Notice that when calling Reflect.get and Reflect.set, we don't pass along the receiver (in fact, we're not using the receiver argument at all in those, so I've commented the parameter out). That means they'll use the target itself as the receiver, which you need if the properties are accessors (like Map's size property) and they need their this to be the actual instance (as Map's size does).
If your goal is just to intercept Map#get and Map#set, though, you don't need a proxy at all. Either:
Create a Map subclass and instantiate that. Assumes you control the creation of the Map instance, though.
Create a new object that inherits from the Map instance, and override get and set; you don't have to be in control of the original Map's creation.
Replace the set and get methods on the Map instance with your own versions.
Here's #1:
class MyMap extends Map {
set(...args) {
console.log("set called");
return super.set(...args);
}
get(...args) {
console.log("get called");
return super.get(...args);
}
}
const m1 = new MyMap();
m1.set("a", "aval");
console.log(m1.get("a"));
#2:
const m1 = new Map();
const p1 = Object.create(m1, {
set: {
value: function(...args) {
console.log("set called");
return m1.set(...args);
}
},
get: {
value: function(...args) {
console.log("get called");
return m1.get(...args);
}
}
});
p1.set("a", "aval");
console.log(p1.get("a"));
#3:
const m1 = new Map();
const m1set = m1.set; // Yes, we know these are `Map.prototype.set` and
const m1get = m1.get; // `get`, but in the generic case, we don't necessarily
m1.set = function(...args) {
console.log("set called");
return m1set.apply(m1, args);
};
m1.get = function(...args) {
console.log("get called");
return m1get.apply(m1, args);
}
m1.set("a", "aval");
console.log(m1.get("a"));
Let me add more to this.
Many built-in objects, for example Map, Set, Date, Promise and others make use of so-called internal slots.
These are like properties but reserved for internal, specification-only purposes. For instance, Map stores items in the internal slot [[MapData]]. Built-in methods access them directly, not via [[Get]]/[[Set]] internal methods. So Proxy can’t intercept that.
For example:
let map = new Map();
let proxy = new Proxy(map, {});
proxy.set('name', 'Pravin'); // Error
Internally, a Map stores all data in its [[MapData]] internal slot. The proxy doesn't have such slot. The built-in method Map.prototype.set method tries to access the internal property this.[[MapData]], but because this=proxy, can't find it in proxy and just fails.
There’s a way to fix it:
let map = new Map();
let proxy = new Proxy(map,{
get(target,prop,receiver){
let value = Reflect.get(...arguments);
return typeof value === 'function'?value.bind(target):value;
}
});
proxy.set('name','Pravin');
console.log(proxy.get('name')); //Pravin (works!)
Now it works fine, because get trap binds function properties, such as map.set, to the target object (map) itself. So the value of this inside proxy.set(...) will be not proxy, but the original map. So when the internal implementation of set tries to access this.[[MapData]] internal slot, it succeeds.
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 was creating a component and was trying to break my implementation. The idea is to not allow user to manipulate the exposed properties.
The implementation was like this:
function MyClass(){
var data = [];
Object.defineProperty(this, 'data', {
get: function(){ return data; },
set: function(){ throw new Error('This operation is not allowed'); },
configurable: false,
});
}
var obj = new MyClass();
try {
obj.data = [];
} catch(ex) {
console.log('mutation handled');
}
obj.data.push('Found a way to mutate');
console.log(obj.data)
As you see, setting the property is handled but user is still able to mutate it using .push. This is because I'm returning a reference.
I have handled this case like:
function MyClass(){
var data = [];
Object.defineProperty(this, 'data', {
get: function(){ return data.slice(); },
set: function(){ throw new Error('This operation is not allowed'); },
configurable: false,
});
}
var obj = new MyClass();
try {
obj.data = [];
} catch(ex) {
console.log('mutation handled');
}
obj.data.push('Found a way to mutate');
console.log(obj.data)
As you see, I'm returning a new array to solve this. Not sure how it will affect performance wise.
Question: Is there an alternate way to not allow user to mutate properties that are of type object?
I have tried using writable: false, but it gives me error when I use it with get.
Note: I want this array to mutable within class but not from outside.
Your problem here is that you are effectively blocking attempts to modify MyClass. However, other objects members of MyClass are still JavaScript objects. That way you're doing it (returning a new Array for every call to get) is one of the best ways, though of course, depending of how frequently you call get or the length of the array might have performance drawbacks.
Of course, if you could use ES6, you could extend the native Array to create a ReadOnlyArray class. You can actually do this in ES5, too, but you lose the ability to use square brackets to retrieve the value from a specific index in the array.
Another option, if you can avoid Internet Explorer, is to use Proxies (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy).
With a proxy, you can trap calls to get properties of an object, and decide what to return or to do.
In the example below, we create a Proxy for an array. As you see in the handler, we define a get function. This function will be called whenever the value of a property of the target object is accessed. This includes accessing indexes or methods, as calling a method is basically retrieving the value of a property (the function) and then calling it.
As you see, if the property is an integer number, we return that position in the array. If the property is 'length' then we return the length of the array. In any other case, we return a void function.
The advantage of this is that the proxyArray still behaves like an array. You can use square brackets to get to its indexes and use the property length. But if you try to do something like proxyArray.push(23) nothing happens.
Of course, in a final solution, you might want decide what to do based on which
method is being called. You might want methods like map, filter and so on to work.
And finally, the last advantage of this approach is that you keep a reference to the original array, so you can still modify it and its values are accessible through the proxy.
var handler = {
get: function(target, property, receiver) {
var regexp = /[\d]+/;
if (regexp.exec(property)) { // indexes:
return target[property];
}
if (property === 'length') {
return target.length;
}
if (typeof (target[property]) === 'function') {
// return a function that does nothing:
return function() {};
}
}
};
// this is the original array that we keep private
var array = [1, 2, 3];
// this is the 'visible' array:
var proxyArray = new Proxy(array, handler);
console.log(proxyArray[1]);
console.log(proxyArray.length);
console.log(proxyArray.push(32)); // does nothing
console.log(proxyArray[3]); // undefined
// but if we modify the old array:
array.push(23);
console.log(array);
// the proxy is modified
console.log(proxyArray[3]); // 32
Of course, the poblem is that proxyArray is not really an array, so, depending on how you plan to use it, this might be a problem.
What you want isn't really doable in JavaScript, as far as I'm aware. The best you can hope for is to hide the data from the user as best you can. The best way to do that would be with a WeakMap
let privateData = new WeakMap();
class MyClass {
constructor() {
privateData.set(this, {
data: []
});
}
addEntry(entry) {
privateData.get(this).data.push(entry);
}
getData() {
return privateData.get(this).data.concat();
}
}
So long as you never export privateData don't export from the module, or wrap within an IIFE etc.) then your MyClass instances will be able to access the data but external forces can't (other than through methods you create)
var myInstance = new MyClass();
myInstance.getData(); // -> []
myInstance.getData().push(1);
myInstance.getData(); // -> []
myInstance.addEntry(100);
myInstance.getData(); // -> [100]
I wrote a small wrapper to return undefined in place of typeError when accessing properties that don't exist using a Proxy. Here is the code:
function proxify(event) {
var proxy = new Proxy(event, {
get: function (target, property) {
if (property in target) {
return target[property];
} else {
return '';
}
}
}
});
return proxy;
}
This works when a property is missing 1 level deep.
For example, assuming obj.something does not exist:
obj.something.else
will return undefined
But if the object property is deep nested
obj.something.else.deeper
I receive a typeError
My question is how do I extend the function above to work on deep nested objects?
Thx
You need to wrap the return value in your proxify function:
function proxify(event) {
return isPrimitive(event) ? event : new Proxy(event, { get: getProp });
}
function isPrimitive(v) {
return v == null || (typeof v !== 'function' && typeof v !== 'object');
}
function getProp (target, property) {
if (property in target) {
return proxify(target[property]);
} else {
return proxify({});
}
}
In all honesty, you're probably better off using something like lodash's _.get() or ramda's R.path(). There's no way to know from the first getProp() call how many layers deep the evaluation is going to go, so it has to always return a primitive value or a "truthy" Proxy instance so that the next property access can be intercepted (if one happens). The _.get() method on the other hand takes a string so it can immediately know from the initial invocation that you're only trying to access down so many levels.
I published a library on GitHub (Observable Slim) that enables you to proxy an object and any nested children of that object. It also has a few extra features:
Reports back to a specified callback whenever changes occur.
Will prevent user from trying to Proxy a Proxy.
Keeps a store of which objects have been proxied and will re-use existing proxies instead of creating new ones (very significant performance implications).
Written in ES5 and employs a forked version of the Proxy polyfill so it can be deployed in older browsers fairly easily and support Array mutation methods.
It works like this:
var test = {testing:{}};
var p = ObservableSlim.create(test, true, function(changes) {
console.log(JSON.stringify(changes));
});
p.testing.blah = 42; // console: [{"type":"add","target":{"blah":42},"property":"blah","newValue":42,"currentPath":"testing.blah",jsonPointer:"/testing/blah","proxy":{"blah":42}}]
As idbehold said, you must return a primitive value or new proxy.
I think you dont need loDash to do that, and Yes, you can manage the depth of the nested proxy ! (see my last point, deepProxy.
But, since I'm not sure what you want to do, i would like focus your attention to those few points:
First usage (simulated chain):
function proxify(event){
return isPrimitive(event) ? event : new Proxy(event,{ get: getProp });
}
function isPrimitive(v){
return v == null || (typeof v !== 'function' && typeof v !== 'object');
}
function getProp(target, property){
return (property in target)
? proxify(target[property])
: proxify({});
}
this code as it stands, will simulate nested properties,
but not confer any members dependency (because no assignment, then no preservation).
obj.something.else = 99;
console.log(obj.something.else) // returning a new Proxy()
IMO, it doesn't make sense, or reserved to very specific cases.
Again, it depend of what is your purpose.
Second usage (deep assignment):
Basic not extensible assignment
function proxify(defprop={})
{
return new Proxy(defprop, handler);
};
var handler =
{
get: function(target, property, receiver)
{
if(!(property in target))
target[property] = proxify();
return Reflect.get(target, property, receiver);
},
};
obj.something.else = 99;
console.log(obj.something.else) // returning 99
Extensible to assignment
By using basic chainable proxy, if you try to set a new object (or nested object) you will only trap the root of this new property.
There is no more possible chainning, this is not suitable and not safe.
Remember the following case:
Cyph:
But if the object property is deep nested
obj.something.else.deeper
(Bad way) Assuming this assignment:
var deeper = {toto: "titi"};
obj.something.else = deeper;
console.log(obj.something.else.toto)
// will return "titi"
obj.something.else.toto.something.else = {tutu: "tata"};
// will return a typeError Again
(Good way) To propagate the chain ability
You need to check for the type of value as a true object in the setter trap, and proxify each root properties. The rest of the depth will be converted naturally by the getter trap.
var handler =
{
get: function(target, property, receiver)
{
if(!(property in target))
target[property] = proxify();
return Reflect.get(target, property, receiver);
},
set: function(target, property, value, receiver)
{
// extend proxify to appended nested object
if(({}).toString.call(value) === "[object Object]")
value = deepApply(receiver, property, value);
return Reflect.set(target, property, value, receiver);
},
};
var deepApply = function(receiver,property, data)
{
var proxy = proxify();
var props = Object.keys(data);
var size = props.length;
for(var i = 0; i < size; i++)
{
property = props[i];
proxy[property] = data[property];
}
return proxy;
};
Manage the depth
I invite you to read my actual solution : deepProxy
The level can be auto-defined via the proto declaration
when the object structure is setted. Then you can easily know the current floor and add conditional instructions for each level.
The path of property can be reach from his accessor.
With the intention to write unit tests, I want to pass an object to my function that mocks any possible property - Note: not a function property.
When I have a function like this:
function someFunc (config){
var something = config.params.innerParams;
}
when called someFunc({}) it will throw Cannot read property 'innerParams' of undefined'.
If there are many recursive properties in config, mocking it may be very time consuming. Is there a way to write a "mock" object that I can pass to my function to mimic any structure? It may assign undefined to all properties at the time of access, like this:
var magic = new Magic(); //magical mock created
var a1 = magic.something; //undefined;
var a2 = magic.something.innerSomething; //undefined
var a3 = magic.something.innerSomething.farAwaySomething; //undefined
All I want is to avoid Cannot read property '*' of undefined' being thrown.
You can use ES6 Proxy to pass access to notexisting property of object.
I would've return the object itself in that case.
But I think, it would be difficult to archive equallity to undefined.
At least I don't know the way (but I have some thoughts).
And don't forget to check Compatibility table.
function Magic() {
var res = new Proxy(this, { get(target, key, receiver) { return res } });
return res;
}
Can't make it equal to undefined, but can to false (nonstrictly):
function Magic() {
var res = new Proxy(this, { get(target, key, receiver) { return key === Symbol.toPrimitive ? Reflect.get(target, key, receiver) : res } });
res[Symbol.toPrimitive] = () => false;
return res;
}
Tested in FF44.
If you have Proxy support (Firefox, IE11, and Edge for now), you can pass in
new Proxy({}, { get(a,b,p) { return p; } })
This is a Proxy object which allows access on properties of any name. When accessed, the value of every such property is the original proxy object itself, thereby allowing infinite chains of property access.
Property access does not yield undefined, but there is no way to do that while simultaneously allowing further property access.
There is a noSuchMethod feature in some javascript implementations (Rhino, SpiderMonkey)
proxy = {
__noSuchMethod__: function(methodName, args){
return "The " + methodName + " method isn't implemented yet. HINT: I accept cash and beer bribes" ;
},
realMethod: function(){
return "implemented" ;
}
}
js> proxy.realMethod()
implemented
js> proxy.newIPod()
The newIPod method isn't implemented yet. HINT: I accept cash and beer bribes
js>
I was wondering, is there was a way to do something similar for properties? I'd like to write proxy classes that can dispatch on properties as well as methods.
UPDATE: ECMAScript 6 Proxies are widely supported now. Basically, if you don't need to support IE11, you can use them.
Proxy objects allow you to define custom behavior for fundamental operations, like property lookup, assignment, enumeration, function invocation, etc.
Emulating __noSuchMethod__ with ES6 Proxies
By implementing traps on property access, you can emulate the behavior of the non-standard __noSuchMethod__ trap:
function enableNoSuchMethod(obj) {
return new Proxy(obj, {
get(target, p) {
if (p in target) {
return target[p];
} else if (typeof target.__noSuchMethod__ == "function") {
return function(...args) {
return target.__noSuchMethod__.call(target, p, args);
};
}
}
});
}
// Example usage:
function Dummy() {
this.ownProp1 = "value1";
return enableNoSuchMethod(this);
}
Dummy.prototype.test = function() {
console.log("Test called");
};
Dummy.prototype.__noSuchMethod__ = function(name, args) {
console.log(`No such method ${name} called with ${args}`);
return;
};
var instance = new Dummy();
console.log(instance.ownProp1);
instance.test();
instance.someName(1, 2);
instance.xyz(3, 4);
instance.doesNotExist("a", "b");
Original 2010 answer
There is only one existing thing at the moment that can actually do what you want, but unfortunately is not widely implemented:
ECMAScript Harmony Proxies.
There are only two working implementations available at this time, in the latest Firefox 4 betas (it has been around since FF3.7 pre-releases) and in node-proxy for server-side JavaScript -Chrome and Safari are currently working on it-.
It is one of the early proposals for the next version of ECMAScript, it's an API that allows you to implement virtualized objects (proxies), where you can assign a variety of traps -callbacks- that are executed in different situations, you gain full control on what at this time -in ECMAScript 3/5- only host objects could do.
To build a proxy object, you have to use the Proxy.create method, since you are interested in the set and get traps, I leave you a really simple example:
var p = Proxy.create({
get: function(proxy, name) { // intercepts property access
return 'Hello, '+ name;
},
set: function(proxy, name, value) { // intercepts property assignments
alert(name +'='+ value);
return true;
}
});
alert(p.world); // alerts 'Hello, world'
p.foo = 'bar'; // alerts foo=bar
Try it out here.
EDIT: The proxy API evolved, the Proxy.create method was removed in favor of using the Proxy constructor, see the above code updated to ES6:
const obj = {};
const p = new Proxy(obj, {
get(target, prop) { // intercepts property access
return 'Hello, '+ prop;
},
set(target, prop, value, receiver) { // intercepts property assignments
console.log(prop +'='+ value);
Reflect.set(target, prop, value, receiver)
return true;
}
});
console.log(p.world);
p.foo = 'bar';
The Proxy API is so new that isn't even documented on the Mozilla Developer Center, but as I said, a working implementation has been included since the Firefox 3.7 pre-releases.
The Proxy object is available in the global scope and the create method can take two arguments, a handler object, which is simply an object that contains properties named as the traps you want to implement, and an optional proto argument, that makes you able to specify an object that your proxy inherits from.
The traps available are:
// TrapName(args) Triggered by
// Fundamental traps
getOwnPropertyDescriptor(name): // Object.getOwnPropertyDescriptor(proxy, name)
getPropertyDescriptor(name): // Object.getPropertyDescriptor(proxy, name) [currently inexistent in ES5]
defineProperty(name, propertyDescriptor): // Object.defineProperty(proxy,name,pd)
getOwnPropertyNames(): // Object.getOwnPropertyNames(proxy)
getPropertyNames(): // Object.getPropertyNames(proxy)
delete(name): // delete proxy.name
enumerate(): // for (name in proxy)
fix(): // Object.{freeze|seal|preventExtensions}(proxy)
// Derived traps
has(name): // name in proxy
hasOwn(name): // ({}).hasOwnProperty.call(proxy, name)
get(receiver, name): // receiver.name
set(receiver, name, val): // receiver.name = val
keys(): // Object.keys(proxy)
The only resource I've seen, besides the proposal by itself, is the following tutorial:
Harmony Proxies: Tutorial
Edit: More information is coming out, Brendan Eich recently gave a talk at the JSConf.eu Conference, you can find his slides here:
Proxies are Awesome!
Here's how to get behaviour similar to __noSuchMethod__
First of all, here's a simple object with one method:
var myObject = {
existingMethod: function (param) {
console.log('existing method was called', param);
}
}
Now create a Proxy which will catch access to properties/method and add your existing object as a first parameter.
var myObjectProxy = new Proxy(myObject, {
get: function (func, name) {
// if property or method exists, return it
if( name in myObject ) {
return myObject[name];
}
// if it doesn't exists handle non-existing name however you choose
return function (args) {
console.log(name, args);
}
}
});
Now try it:
myObjectProxy.existingMethod('was called here');
myObjectProxy.nonExistingMethod('with a parameter');
Works in Chrome/Firefox/Opera. Doesn't work in IE(but already works in Edge). Also tested on mobile Chrome.
Creation of proxy can be automated and invisible i.e. if you use Factory pattern to build your objects. I did that to create workers which internal functions can be called directly from the main thread. Using workers can be now so simple thanks to this cool new feature called Proxy. The simplest worker implementation ever:
var testWorker = createWorker('pathTo/testWorker.js');
testWorker.aFunctionInsideWorker(params, function (result) {
console.log('results from worker: ', result);
});
I don't believe this type of metaprogramming is possible (yet) in javascript. Instead, try using the __noSuchMethod__ functionality to achieve the effect with property getters. Not cross-browser as it's a Mozilla extension.
var proxy = {
__noSuchMethod__: function(methodName, args) {
if(methodName.substr(0,3)=="get") {
var property = methodName.substr(3).toLowerCase();
if (property in this) {
return this[property];
}
}
}, color: "red"
};
alert(proxy.getColor());
You can use the Proxy class.
var myObj = {
someAttr: 'foo'
};
var p = new Proxy(myObj, {
get: function (target, propName) {
// target is the first argument passed into new Proxy,
// in this case target === myObj
return 'myObj with someAttr:"' + target.someAttr
+ '" had "' + propName
+ '" called on it.';
}
});
console.log(p.nonExsistantProperty);
// outputs:
// myObj with someAttr:"foo" had "nonExsistantProperty" called on it
There is __defineGetter__, __defineSetter__, __lookupGetter__ and __lookupSetter__ in addition to __noSuchMethod__ in SpiderMonkey.
Although this is an old question I was looking into this today. I wanted to be able to seamlessly integrate code from another context, maybe a different web page or server.
Its the sort of thing that breaks in the long run, but I think its an interesting concept none the less. These things can be useful for mashing code together quickly, ( which then exists for years, buried somewhere ).
var mod = modproxy();
mod.callme.first.now('hello', 'world');
mod.hello.world.plot = 555;
var v = mod.peter.piper.lucky.john.valueOf;
console.log(v);
mod.hello.world = function(v) {
alert(v);
return 777;
};
var v = mod.hello.world('funky...');
console.log(v);
var v = mod.hello.world.plot.valueOf;
console.log(v);
mod.www.a(99);
mod.www.b(98);
function modproxy(__notfound__) {
var mem = {};
return newproxy();
function getter(target, name, receiver, lname) {
if(name === 'valueOf') {
lname=lname.slice(1);
if(lname in mem) {
var v = mem[lname];
console.log(`rd : ${lname} - ${v}`);
return v;
}
console.log(`rd (not found) : ${lname}`);
return;
}
lname += '.'+name;
return newproxy(() => {}, lname);
} // getter
function setter(obj, prop, newval, lname) {
lname += '.' + prop;
lname = lname.slice(1);
console.log(`wt : ${lname} - ${newval}`);
mem[lname] = newval;
} // setter
function applyer(target, thisArg, args, lname) {
lname = lname.slice(1);
if(lname in mem) {
var v = mem[lname];
if(typeof v === 'function') {
console.log(`fn : ${lname} - [${args}]`);
return v.apply(thisArg,args);
}
return v;
}
console.log(`fn (not found): ${lname} - [${args}]`);
} // applyer
function newproxy(target, lname) {
target = target || {};
lname = lname || '';
return new Proxy(target, {
get: (target, name, receiver) => {
return getter(target, name, receiver, lname);
},
set: (target, name, newval) => {
return setter(target, name, newval, lname);
},
apply: (target, thisArg, args) => {
return applyer(target, thisArg, args, lname);
}
});
} //proxy
} //modproxy
I implemented a valueOf step to read values, because it seems the property is 'get-ted' first.
I dont think its possible to tell at the time the property is 'get-ted' if its going to be invoked or read ( or required for further chaining ).
Its ok for single level properties. The property is either there or its not and either the required type or its not.
I'll work on it further, looking at promises for async/await routines, proxying existing objects and finer control of how properties are accessed when I am more familiar with how the code behaves and is best implemented.
I created a repository on GitHub: modproxy.js/README.md
CodePen: modproxy.js
My original question:
does javascript have an equivalent to the php magic class __call