JavaScript Equivalent Of PHP __call
It is possible using the ES6 Proxy API:
var myObj = {};
var myProxy = new Proxy(myObj, {
get: function get(target, name) {
return function wrapper() {
var args = Array.prototype.slice.call(arguments);
console.log(args[0]);
return "returns: " + args[0];
}
}
});
console.log(myProxy.foo('bar'));
Browser compatibility is available on MDN. As of August 2017 all browsers (including Microsoft Edge) except Internet Explorer support it.
See this answer for a more complete look at Proxy.
Obsolete since Gecko 43 (Firefox 43 / Thunderbird 43 / SeaMonkey 2.40)
You can use __noSuchMethod__
in Firefox. Unfortunately it is non standard...
Related question : Is there an equivalent of the __noSuchMethod__ feature for properties, or a way to implement it in JS?
No. Due to the way JavaScript works, the equivalent would be like Python's __getattr__
/__getitem__
, rather than PHP's __call
, as it would need to be dealt with when retrieving the attribute rather than when calling it.
Then, you can look at a question like Python's __getattr__ in Javascript which answers it in that way.
See also such questions as these:
- JavaScript's equivalent to PHP's __get() magic method
- JavaScript getter for all properties
To build upon @amirnissim's answer a slight bit.
As most of us are probably already aware, ES6 introduces the Proxy API, which allows us to create an object (the Proxy object) that traps calls to that object, whereby we are given an opportunity to "route" the attribute the user called on the object to whatsoever we may wish.
Mimicking PHP's Magic Methods
There is unfortuantely no way to extend a class using the Proxy object, but what we can do is set up an intermediary step to turn an object into a proxy, and route any incoming method calls to the method available on the object itself:
class MyProxy
{
constructor ()
{
return this.asProxy()
}
/**
* Return as a proxy with this object as its target.
*/
asProxy ()
{
let handler = {
/**
* This function is called whenever any property on the Proxy
* is called.
*
* @param target the "parent" object; the object the proxy
* virtualizes
* @param prop the property called on the Proxy
*/
get: function (target, prop)
{
/* This will return the property on the "parent" object
*/
if (typeof target[prop] !== 'undefined')
return target[prop]
// TODO: implement custom logic
}
}
return new Proxy(this, handler)
}
}
This essentially gives you the same functionality to PHP's magic __get
method and __call
method at the same time. As for the __call
version, we are simply returning a function for the user to enter arguments into.
Demonstrating the Above
In order to use this, let us first add a bit of custom logic to the place where the TODO: implement custom logic
resides:
if (prop === 'helloWorld')
return function () { console.log("Hello, world!") }
else
return function () { console.log("Where art thou, hello world?") }
If we then go ahead and create a new instance of the MyProxy
class, we can trigger the custom logic we implemented:
let myProxy = new MyProxy()
myProxy.test()
myProxy.hello()
myProxy.helloWorld()
The above example outputs:
Where art thou, hello world?
Where art thou, hello world?
Hello, world!
It would, of course, also be possible to return any other type of value from the get
function, we could just as well return a string or an integer.
Ease of Use; Usage Through Inheritance
In order to make this even easier to use, may I suggest wrapping the asProxy
method into another class, then simply extending any class that needs the "magic method" functionality with the class containing the asProxy
method? By simply returning the asProxy
method from the constructor, you are basically given the same functionality you would see in PHP, in JavaScript.
Of course, it would also be somewhat required that the get method
is somewhat editable so that custom logic can still be handled from the subclass. Perhaps by sending in a closure to the return this.asProxy(() => {})
that is then called from the get
function itself? Or perhaps even route the get
function to a get
method present on the target
object?
Do keep in mind, however, this is only ever applicable in ES6. Transpilers such as Babel cannot, and I quote:
Due to the limitations of ES5, Proxies cannot be transpiled or polyfilled.
The solution presented above does however work perfectly fine as long as this condition is met. It is, for instance, a perfectly viable option in Node.js.