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 am running into an issue where I am getting an error for properties that I've added to an object via Object.defineProperty.
The error in question.
Exception: RangeError: Maximum call stack size exceeded
Maybe (likely) my design is incorrect and I should be doing something differently. This is what I intend to do with the code below:
Create an object P via a factory function.
Pass a config object C to the factory to customise P.
Store C within P as a private object and get/set the values of C by attaching its properties to P via Object.defineProperty. C may be different for any given P.
The problem comes when I want to override the default get/set methods for some C.a
I do that as follows:
// Create P with a custom (non-default) get method.
let C = { a: 1, b: 2, c: 3 };
let P = factory.createObject(C);
const customGetA = function(object, property) {
return function() {
if(!object[property])
object[property] = ' ';
return object[property];
};
};
P.customgGetMethod('a', customGetA);
// Looking at object in the console reveals the error mentioned above.
let factory = (function() {
'use strict';
this.createObject = function(config) {
const product = {};
let C = config;
// Add default getters/setters to the product, referencing the properties of C.
for (const property in config) {
Object.defineProperty(product, property, {
get: function() {
return C[property];
},
set: function(value) {
C[property] = value;
},
configurable: true,
enumerable: true
});
}
product.customGetMethod = function(property, callback) {
// Get the property description.
let descriptor = Object.getOwnPropertyDescriptor(this, property);
// Assign the custom get method within the callback, binding its values via closure.
descriptor.get = callback(this, property);
// Redefine the property with the new get method.
Object.defineProperty(this, property, descriptor);
};
return product;
};
})();
In the end, I want a to be able to pass a custom data object into P and have it remain private, and dynamically generate get/set methods based off of that data so I don't have to get/set boiler plate for N-properites * M-products. This may not be the best design or implementation, but I am at a loss for how to do it another way.
Any alternatives or insight would be appreciated.
The getter function that customGetA creates in P.customgGetMethod('a', customGetA); is essentially
function() {
if(!product.a)
product.a = ' ';
return product.a;
}
When we compare that to the default getter created in the factory
function() {
return C.a;
}
we can see that the new one looks up the value in product, not the configuration C. And looking up a property in product evaluates its getter, which is the function we already are in, which recurses until it eventually overflows the stack...
I think you are looking for
// Assign the custom get method within the callback, binding its values via closure.
descriptor.get = callback(C, property);
// ^
to close over the internal configuration object.
This question already has answers here:
Why is Proxy to a Map object in ES2015 not working
(2 answers)
Closed 5 years ago.
I use a Map javascript object. I'd like to observe changes to a Map instance using a Proxy object. However, whatever handler object I try to feed in to the proxy, I consistently get the error Method Map.prototype.set called on incompatible receiver. What am I doing wrong? The idiomatic example I expect to work looks like
var map = new Map();
var proxy = new Proxy(map, {
set(obj, prop, value) {
console.log(value);
}
});
proxy.set(1, 1); --> error
I also tried setting a handler for apply, but to no avail.
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. Maps 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);
I currently have a Proxy object that I want to capture property calls to if the property is not defined.
A basic version of my code would be something like this.
var a = new Proxy({}, {
get: function(target, name, receiver) {
if (target in name) {
return target[name];
} else {
function a() {
return arguments;
}
var args = a();
return [target, name, receiver, args];
}
}
});
Property calls to a here (i.e: a.b; a.c() etc) should return the target, name, receiver and arguments of the property call.
The problem I wish to solve, however, requires me to know whether the property call is for a property or a function, such that I can apply different treatments to each. Checking the length of the arguments object does not work, as calling a.c() would yield a length of 0 just like a.b, so it would be treated as a plain property and not a method.
Is there a way, therefore, to identify whether the property attempting to be accessed is being called as a function or not.
UPDATE: I should clarify, this method needs to work if the accessed property/method is undefined, as well as existing.
It's possible in a very hacky way. We return a function if the property is undefined. If this function is called, then we know the user was trying to call the property as a function. If it never is, it was called as a property. To check if the function was called, we take advantage of the fact that a Promise's callback is called in the next iteration of the event loop. This means that we won't know if it's a property or not until later, as the user needs a chance to call the function first (as our code is a getter).
One drawback of this method is that the value returned from the object will be the new function, not undefined, if the user was expecting a property. Also this won't work for you if you need the result right away and can't wait until the next event loop iteration.
const obj = {
func: undefined,
realFunc: () => "Real Func Called",
prop: undefined,
realProp: true
};
const handlers = {
get: (target, name) => {
const prop = target[name];
if (prop != null) { return prop; }
let isProp = true;
Promise.resolve().then(() => {
if (isProp) {
console.log(`Undefined ${name} is Prop`)
} else {
console.log(`Undefined ${name} is Func`);
}
});
return new Proxy(()=>{}, {
get: handlers.get,
apply: () => {
isProp = false;
return new Proxy(()=>{}, handlers);
}
});
}
};
const proxied = new Proxy(obj, handlers);
let res = proxied.func();
res = proxied.func;
res = proxied.prop;
res = proxied.realFunc();
console.log(`realFunc: ${res}`);
res = proxied.realProp;
console.log(`realProp: ${res}`);
proxied.propC1.funcC2().propC3.funcC4().funcC5();
Would the typeof operator work for you?
For example:
if(typeof(a) === "function")
{
...
}
else
{
...
}
You can't know ahead of time whether it's a call expression or just a member expression, but you can deal with both situations simultaneously.
By returning a proxy targeting a deep clone of the original property that reflects all but two trap handlers to the original property, you can either chain or invoke each member expression.
The catch is that the proxy target also needs to be callable so that the handler.apply trap does not throw a TypeError:
function watch(value, name) {
// create handler for proxy
const handler = new Proxy({
apply (target, thisArg, argsList) {
// something was invoked, so return custom array
return [value, name, receiver, argsList];
},
get (target, property) {
// a property was accessed, so wrap it in a proxy if possible
const {
writable,
configurable
} = Object.getOwnPropertyDescriptor(target, property) || { configurable: true };
return writable || configurable
? watch(value === object ? value[property] : undefined, property)
: target[property];
}
}, {
get (handler, trap) {
if (trap in handler) {
return handler[trap];
}
// reflect intercepted traps as if operating on original value
return (target, ...args) => Reflect[trap].call(handler, value, ...args);
}
});
// coerce to object if value is primitive
const object = Object(value);
// create callable target without any own properties
const target = () => {};
delete target.length;
delete target.name;
// set target to deep clone of object
Object.setPrototypeOf(
Object.defineProperties(target, Object.getOwnPropertyDescriptors(object)),
Object.getPrototypeOf(object)
);
// create proxy of target
const receiver = new Proxy(target, handler);
return receiver;
}
var a = watch({ b: { c: 'string' }, d: 5 }, 'a');
console.log(a('foo', 'bar'));
console.log(a.b());
console.log(a.b.c());
console.log(a.d('hello', 'world'));
console.log(a.f());
console.log(a.f.test());
Open Developer Tools to view Console.
The Stack Snippets Console attempts to stringify the receiver in a weird way that throws a TypeError, but in the native console and Node.js it works fine.
Try it online!
Some ideas I've come up with, which achieve a similar result at a small cost:
A
typeof(a.b) === "function" //`false`, don't call it.
typeof(a.c) === "function" //`true`, call it.
//Maybe you're not intending to try to call non-functions anyways?
a.c();
B
get: function(target, property) {
//For this, it would have to already be set to a function.
if (typeof(target[property] === "function") {
}
}
C
a.b;
//Simply change the structuring a little bit for functions, e.g.:
a.func.c();
//Then, `func` would be set and handled as a special property.
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)
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