Implement a method with the computed name [Invokable.call] with any
signature of your choice.
End the constructor with return Invokable.create(this)
Also works with a plain object:
import{Invokable}from'invokable';
constRect=(width,height)=>
Invokable.create({
width,
height,
getarea(){
returnthis.width*this.height;
},
[Invokable.call](depth=1){
returnthis.area* depth;
},
});
Declare a property with the computed name [Invokable.call] with any
function of your choice.
Pass the entire object to Invokable.create
Capabilities and Limitations
Invokable.create can:
Preserve the this context of the object.
Do the right thing when an object's [Invokable.call] method has been
replaced at runtime.
Replicate all properties without triggering getters and setters.
Inherit the same prototype, ensuring things like constructor and the
instanceof operator still work.
Create functions whose name property is writable. Simply define a name
property in the original target object.
It cannot:
Modify the original object. A new object that masquerades as the original is
returned.
Do anything with an object that does not implement [Invokable.call]. A
TypeError is thrown when such an object is given.
Work on JavaScript engines that don't support Object.setPrototypeOf,
Object.getPrototypeOf, Object.getOwnPropertyDescriptors, and
Object.defineProperties, unless they have been polyfilled.
Be rebound using .bind(...). However, the [Invokable.call] method can
still be rebound.
API
Invokable.create(target) takes a target object that conforms to the
Invokable interface and returns a new object that is a function that
masquerades as the original target object. All properties, values, getters,
setters, or otherwise are replicated, including the prototype.
Normal function objects have a read-only property called name. However, if
the target object defines its own value or getter called name, then the
name property will be made writable in the result. In all cases, the name
property will default to the original function or surrounding class name.
If the target object does not implement [Invokable.call], a TypeError
is thrown.
Invokable.call is a tag that denotes callability. It is a constant used by
an object to conform to the Invokable interface. Its current value is the
string __call__, but it may become a symbol in the future.
TypeScript Support
TypeScript support comes out of the box without any additional setup. Here is
the TypeScript version of the class example:
Note that it is necessary to modify the interface generated by the class in
order make class instances callable to the type system.
The TypeScript version of the plain object example:
import{Invokable}from'invokable';
constRect=(width:number,height:number)=>
Invokable.create({
width,
height,
getarea(){
returnthis.width*this.height;
},
[Invokable.call](depth=1){
returnthis.area*depth;
},
});
Performance
The following observations are based on the benchmarks listed further below.
Creating an invokable object is very slow. Doing so for a plain object incurs
around a 7x slowdown, whereas doing so for a class instance can suffer from
around a 700x slowdown. Therefore, avoid performing a huge number of calls to
Invokable.create in a hot code path.
Property access, regardless of whether or not through the prototype, is not
significantly impacted.
Invoking the invokable object itself is also not guaranteed to be faster than
directly invoking the method it points to.
To run these on your machine, run npm run bench. The process typically takes
a few minutes, and directly writes results to STDOUT as they finish running.
Results were measured on an Intel Core M @ 1.2GHz with 8GB of DDR3-1600 on
Node.JS v8.3.0. Throughput numbers are expressed in operations per second (ops).