Article translated from source: https://hackernoon.com/creating-callable-objects-in-javascript-d21l3te1
Callable object
are data structures that act as both object
and function
. You can access
and assign the obj.bar
property, call obj.foo ()
, call obj ()
, as if it were a function
.
Direct calling is like calling an obj
function
that changes the properties of an object through context
.
If you have experience with Python
, you will realize this, there is a built-in Python protocol using the __call__ class method
. Any method assigned to __call__
can be called obj.__call __ ()
or obj()
.
Callable Objects
can also be considered stateful function
. Functions are inherently instance statless procedures
.
In JavaScript
almost everything is an object
, including functions, so we can certainly do this, but how? It is not built into a language like Python
, but there are several ways to make it work.
The Challenge
Inspired by Python
, create a Class constructor
that can be used to create _call
objects to redirect to a method named _call
. This redirection makes it possible to inherit from the Class
and easily override
and extend
_call
method
with the new functionality, without having to worry about the inner operation of the _call
object.
To do this, we will need to inherit from the Function
constructor, inherit from the Object
and allow creation of both object
and dynamic function
.
The main obstacle is giving a function object that references itself.
To have a reference
to the __call method
, the function
part of the function
object created by the Callable class constructor
must have a reference
for itself.
The Solutions
Creating an extensible Callable class
maintains proper and correct inheritance in JavaScript
and allows calling objects that it constructs to function, with references to themselves, redirecting to the overridable method _call
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 'use strict' class Callable extends Function { constructor() { super('...args', 'return this._bound._call(...args)') // Or without the spread/rest operator: // super('return this._bound._call.apply(this._bound, arguments)') this._bound = this.bind(this) return this._bound } _call(...args) { console.log(this, args) } } |
Because inheriting from Function
, it is possible to create dynamic function
from string
, using super
in the constructor. So, the string being passed to super
will be the body
of fucntion
. That Fucntion
can access its own object and call a _call
method, passing its argument
. Do this by using bind
.
Bind method
will set this context
of a function for whatever we want, by wrapping that function in a bound function. So bind
the function to itself by this.bind(this)
.
Callable object
has a reference
to itself, except the object returned from the constructor
, returned by this
. So all the properties will be appended to it, our function has a reference to the old object passed to bind
.
An easy solution to this problem is to attach a reference to the new object
wrapped on the old object
by _bound
. And the body
of the function
, in the string
going to super
, just call _call
, using this._bound reference
.
The Callee Way
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 'use strict' class Callable extends Function { constructor() { super('return arguments.callee._call.apply(arguments.callee, arguments)') // We can't use the rest operator because of the strict mode rules. // But we can use the spread operator instead of apply: // super('return arguments.callee._call(...arguments)') } _call(...args) { console.log(this, args) } } |
Again use super
call to create a dynamic function, which is referenced to the function itself by taking advantage of another hidden variable within a function.
The argument object with argument.callee
is a reference to the called function. Use this reference
as the first argument to link functions.
So inside the function body, the string is passed to super
, just call _call
in arguments.callee
.
The Closure & Prototype Way
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 'use strict' class Callable extends Function { constructor() { var closure = function(...args) { return closure._call(...args) } // Or without the spread/rest operator: // var closure = function() { // return closure._call.apply(closure, arguments) // } return Object.setPrototypeOf(closure, new.target.prototype) } _call(...args) { console.log(this, args) } } |
Here instead of creating a dynamic function with siêu
, we remove the function object created by the constructor
( this object
) and replace it with closure
, by returning instead of this
from the constructor
.
closure
is also a function object
and can reference itself in the body
via the closure
variable. Use reference closure
to redirect the call to its __call
method.
But we broke the string by replacing this
with closure
, and re-attaching the constructor
‘s prototype
to closure
using Object.setPrototypeOf
and new.target
(which is a reference to the constructor) to get the prototype
.
You can use this.constructor.prototype
instead of new.target.prototype
. But when calling the first time to create the object
, it is quite meaningful.
The Proxy Way
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 'use strict' class Callable extends Function { constructor() { super() return new Proxy(this, { apply: (target, thisArg, args) => target._call(...args) }) } _call(...args) { console.log(this, args) } } |
Using Proxy, we can block calls to fucntion
, using apply
, and redirect it to another function. The apply
allows to call it a reference to itself as the target argument
.
So we created a Class
that inherits Fucntion
, Callable
, packages Fucntion
objects created in a Proxy
, any calls made to those objects and redirects them to _call
function on the object itself. , use target
.
Thanks and hope the article is useful in your work.