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
}
Related
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 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.
I was trying to be a sneaky developer, and I done got myself lost in ES6 Proxies. Basically I wanted to capture any get or set in a property from another class I wrote and make sure they are stored somewhere else besides the object. It looks like this:
'use strict'
class MyObject()
{
superAwesomeFunction(){//super awesome code goes here}
}
const myProxyObject = new Proxy(MyObject, {
get: function(target, name, receiver)
{
if([CONDITIONS THAT MEET MY DEMANDS])
{
// deliver property values if they are
// my super cool database (if they exist)
}
// else just ya know, forward it to the actual MyObject
// property, if it's an actual property on MyObject
return target[name];
},
set: function(target, name, value)
{
if([CONDITIONS THAT MEET MY DEMANDS])
{
// set property values if they are
// to super cool database
}
else
{
// just set the value to the property, which
// gets created if it doesn't exist (WHICH IS SO COOL)
target[name] = value;
}
return true;
}
Okay the cool part about this? you can do something like:
// property that doesn't exist (yet)
console.log(myProxyObject.wholivesinapineappleunderthesea);
// but wait for it...
myProxyObject.wholivesinapineappleunderthesea = 'spongebob';
// bam! now it exists on the object, AND lives in my DB!
console.log(myProxyObject.wholivesinapineappleunderthesea);
Which, while a lot of work for something dorky, i cannot explain how happy it makes me feel. However, there is a problem with this. Remember that superAwesomeFunction() I put in MyObject()? Well whenever I try to call it now ES6 gives me this load of sadness:
myProxyObject.superAwesomeFunction is not a function
ES6 sits on a throne of LIES! It's totally there right? Okay so I'm pretty sure I'm trapping something wrong, because when I debug I see that the get section of the Proxy is actually picking up the superAwesomeFunction call (which makes sense as superAwesomeFunction is a property that contains a function (){})
HERE'S MY QUESTION: Does anybody know of any solution that will let me keep my ridiculous on the fly properties and still call my superAwesomeFunction()? Is it the apply trap?
To extend on Sam's answer.
The following succeeds:
function Foo() {
this.bar = function() { console.log('baz'); }
}
let foo = new Proxy(new Foo(), {});
foo.bar();
However, when the get trap is added to the handler, "is not a function" is thrown:
function Foo() {
this.bar = function() { console.log('baz'); }
}
let foo = new Proxy(new Foo(), {
get: function(target, prop) {}
});
foo.bar();
The solution here is to add a typeof check for default handling of the target's existing functions:
function Foo() {
this.bar = function() { console.log('baz'); }
}
let foo = new Proxy(new Foo(), {
get: function(target, prop) {
if (typeof target[prop] === 'function') {
return target[prop].bind(target);
}
}
});
foo.bar();
The following articles gives some more examples and provides nice boilerplate to use with Proxy handlers: Safely Extending The JavaScript Set Object Using Proxies
It is a little syntax error there ... you are wrapping the proxy around the raw class, not an object.
Try:
const myProxyObject = new Proxy(new MyObject(), { ... });
This should be ok. For example:
> class Blah {
... run() {
..... console.log('yay')
..... }
... }
[Function: blah]
> myProx = new Proxy(new Blah(), {});
blah {}
> myProx.run()
yay
Full code for answer is here, thank you everyone!
'use strict'
class MyObject()
{
superAwesomeFunction(){
console.log("this works!");
}
}
const myProxyObject = new Proxy( new MyObject(), {
get: function(target, name, receiver)
{
if([CONDITIONS THAT MEET MY DEMANDS])
{
// do something cool for yourself
}
return target[name];
},
set: function(target, name, value)
{
if([CONDITIONS THAT MEET MY DEMANDS])
{
// do something cool for yourself
}
else
{
// just set the value to the property, which
// gets created if it doesn't exist (WHICH IS SO COOL)
target[name] = value;
}
return true;
}
});
String.prototype.contains = function(str) {
return this.indexOf(str) !== -1;
};
This snippet does extend String.prototype.
It works well with function calls like 'foobar'.contains('foo');
But it doesn't work well if it's passed as a function, not invoking it:
var str = 'foobar';
['foo', 'bar'].every(str.contains);
TypeError: Object [object global] has no method 'indexOf'
I know you could do:
['foo', 'bar'].every(str.contains.bind(str));
or something like:
['foo', 'bar'].every(function(item) { return str.contains(item); });
But they just make me feel awkward.
In a Constructor function I created, although it doesn't work this way:
function Foo(myStr) {
this.myStr = myStr;
this.contains = function(fragment) {
return this.myStr.indexOf(fragment) !== -1;
};
}
var someFoo = new Foo('foobar');
['foo', 'bar'].every(someFoo.contains)
TypeError: Cannot call method 'indexOf' of undefined
I could do this to make it work:
function Foo(myStr) {
var self = this; // Line added
this.myStr = myStr;
this.contains = function(fragment) {
return self.myStr.indexOf(fragment) !== -1; // this changed to self
};
}
I'm wondering if there's a way to extend String.prototype so that I don't have to bind 'someString'.contains to 'someString' every time I use it as a function without invoking it.
Try this:
Object.defineProperty(String.prototype, "contains", {
get: function () {
return (function(str) {
return this.indexOf(str) !== -1;
}).bind(this);
}
});
It defines a new property with a getter, meaning every time you "get" the function, for example in ['foo', 'bar'].every(str.contains);, the function defined above would get called and the return value of that function would be returned as str.contains, in this case. The function is the same as yours, only we bind this in the getter which is the string, which is what we want.
If Object.defineProperty isn't defined in your browser (or anyone to use your code), you can use __defineGetter__ instead.
A complete solution would look like this:
addPropertyWithGetter = function(object, property) {
getter = function () {
return (function(str) {
return this.indexOf(str) !== -1;
}).bind(this);
}
if (_.isFunction(Object.defineProperty)) {
return Object.defineProperty(object, property, {
get: getter,
});
} else {
object.__defineGetter__(property, getter);
}
}
And the usage:
addPropertyWithGetter(String.prototype, "contains")
The second parameter for Array.prototype.every() is the thisArg(Value to use as this when executing callback).
So you could do with:
['foo', 'bar'].every(str.contains, str);
For fixing your code:
function Foo(myStr) {
this.contains = function(fragment) {
// myStr is accessible here
return myStr.indexOf(fragment) !== -1;
};
}
var someFoo = new Foo('foobar');
['foo', 'bar'].every(someFoo.contains);
Is there a way to make any function output a console.log statement when it's called by registering a global hook somewhere (that is, without modifying the actual function itself) or via some other means?
Here's a way to augment all functions in the global namespace with the function of your choice:
function augment(withFn) {
var name, fn;
for (name in window) {
fn = window[name];
if (typeof fn === 'function') {
window[name] = (function(name, fn) {
var args = arguments;
return function() {
withFn.apply(this, args);
return fn.apply(this, arguments);
}
})(name, fn);
}
}
}
augment(function(name, fn) {
console.log("calling " + name);
});
One down side is that no functions created after calling augment will have the additional behavior.
As to me, this looks like the most elegant solution:
(function() {
var call = Function.prototype.call;
Function.prototype.call = function() {
console.log(this, arguments); // Here you can do whatever actions you want
return call.apply(this, arguments);
};
}());
Proxy Method to log Function calls
There is a new way using Proxy to achieve this functionality in JS.
assume that we want to have a console.log whenever a function of a specific class is called:
class TestClass {
a() {
this.aa = 1;
}
b() {
this.bb = 1;
}
}
const foo = new TestClass()
foo.a() // nothing get logged
we can replace our class instantiation with a Proxy that overrides each property of this class. so:
class TestClass {
a() {
this.aa = 1;
}
b() {
this.bb = 1;
}
}
const logger = className => {
return new Proxy(new className(), {
get: function(target, name, receiver) {
if (!target.hasOwnProperty(name)) {
if (typeof target[name] === "function") {
console.log(
"Calling Method : ",
name,
"|| on : ",
target.constructor.name
);
}
return new Proxy(target[name], this);
}
return Reflect.get(target, name, receiver);
}
});
};
const instance = logger(TestClass)
instance.a() // output: "Calling Method : a || on : TestClass"
check that this actually works in Codepen
Remember that using Proxy gives you a lot more functionality than to just logging console names.
Also this method works in Node.js too.
If you want more targeted logging, the following code will log function calls for a particular object. You can even modify Object prototypes so that all new instances get logging too. I used Object.getOwnPropertyNames instead of for...in, so it works with ECMAScript 6 classes, which don't have enumerable methods.
function inject(obj, beforeFn) {
for (let propName of Object.getOwnPropertyNames(obj)) {
let prop = obj[propName];
if (Object.prototype.toString.call(prop) === '[object Function]') {
obj[propName] = (function(fnName) {
return function() {
beforeFn.call(this, fnName, arguments);
return prop.apply(this, arguments);
}
})(propName);
}
}
}
function logFnCall(name, args) {
let s = name + '(';
for (let i = 0; i < args.length; i++) {
if (i > 0)
s += ', ';
s += String(args[i]);
}
s += ')';
console.log(s);
}
inject(Foo.prototype, logFnCall);
Here's some Javascript which replaces adds console.log to every function in Javascript; Play with it on Regex101:
$re = "/function (.+)\\(.*\\)\\s*\\{/m";
$str = "function example(){}";
$subst = "$& console.log(\"$1()\");";
$result = preg_replace($re, $subst, $str);
It's a 'quick and dirty hack' but I find it useful for debugging. If you have a lot of functions, beware because this will add a lot of code. Also, the RegEx is simple and might not work for more complex function names/declaration.
You can actually attach your own function to console.log for everything that loads.
console.log = function(msg) {
// Add whatever you want here
alert(msg);
}