visit
var obj = new CallableObject(); obj(args);
A callable object is a data structure that behaves as both an object and a function. You can access and assign properties
obj.bar
, call methods obj.foo()
, but also call the object directly obj()
, as if it were a function.The direct call is like calling a method of
obj
which has access to the object’s properties through its this
.If you have experience with Python you’ll recognize this, there is a builtin Python protocol using the
__call__
class method. Any method assigned to __call__
can be accessed as obj.__call__()
or as obj()
.Callable Objects can also be thought of as stateful functions. Functions are inherently single instance, stateless procedures. Callable Objects are instantiated, stateful procedures.
In JavaScript almost everything is an object, including functions, so surely we can do this, but how? It’s not built in to the language like Python, but there are several ways to make it work.Taking inspiration from Python, we want to create a Class / constructor that we can use to create callable objects that redirect their calls to a method named
_call
. We want this redirect so that we can inherit from our Class and easily override and extend the _call
method with new functionality, without having to worry about the inner workings of the callable object.To do this, we’re going to need to inherit from the constructor, which inherits from , and allows us to create both an object and a dynamic function.Our main hurdle is giving a function object a reference to itself.In order to have a reference to the
_call
method, the function part of our function object, generated by our Callable class / constructor, must have a reference to itself.We want to create an extensible
Callable
class that maintains proper and correct inheritance in JavaScript, and allows us to call the objects it constructs as functions, with a reference to themselves, redirecting those calls to an overridable method _call
.'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)
}
}
Try it out on
Because we’re inheriting from we can create dynamic functions from strings, using in our constructor. So the string we pass to
super
will be the body of our function. We want that function to be able to access its own object and call a method _call
, passing on its arguments. We do this using .The bind method will allow us to set the
this
context of a function to whatever we want, by wrapping that function in a transparent bound function. So we bind the function to itself with this.bind(this)
.Now our callable object has a reference to itself, except the object we return from our constructor, returned by
this.bind
, is a wrapped version of our original object. So all our properties will be attached to that, new, wrapped function object, and our function has a reference to the old object passed to bind
.The easy solution to this is to attach a reference to the new wrapped object on the old object as
_bound
. And the body of our function, in the string passed to super
, simply calls _call
using the this._bound
reference.Pros
Cons
'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)
}
}
Try it out on
Again we use the
super
call to create a dynamic function, but his time we get our reference to the function itself by taking advantage of another implicit variable inside a function, the object.The arguments object has a property that is a reference to the called function. We use this reference as the first argument to
apply
which binds the functions this
context to itself.So inside the function body, the string passed to super, we only need to call the
_call
method on arguments.callee
.Pros
Cons
arguments
and arguments.callee
are unavailable in ‘strict mode’, see for more.arguments
for the / operator.'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)
}
}
Try it out on
Here instead of creating a dynamic function with
super
, we discard the function object created by the constructor ( the this
object ), and replace it with a , by returning it instead of this
from the constructor
.The closure is also a function object, and can reference itself in its body through the closed over
closure
variable. We use the closure
reference to redirect calls to its method _call
. But we’ve broken the prototype chain by replacing
this
with closure
, so we reattach the prototype of the constructor to closure
using and (which is a reference to the constructor) to get the prototype.You can use
this.constructor.prototype
instead of new.target.prototype
, but then you must first call super
to create the this
object, which is wasteful.Pros
Proxy
or bind
.Cons
'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)
}
}
Try it out on
Using we can intercept calls to a function, using the
apply
trap, and redirect it to another function. The apply
trap gives our callable a reference to itself as the target
argument.So we create a Class that inherits from Function,
Callable
, wrapping the callable objects created in a Proxy
, trapping any calls made to those objects, and redirecting them to the _call
method on the object itself, using the target
reference.Pros
Cons
Callable
in a Proxy
.Proxy
handlers.References: