Skip to content

Instantly share code, notes, and snippets.

@arccoza
Last active March 6, 2023 21:38
Show Gist options
  • Save arccoza/471ec4a2dfc246c703948db6e99ebb9f to your computer and use it in GitHub Desktop.
Save arccoza/471ec4a2dfc246c703948db6e99ebb9f to your computer and use it in GitHub Desktop.
Javascript Callable Object Class / Constructor created by arccoza - https://repl.it/EbN5/18
// This is my approach to creating callable objects
// that correctly reference their object members,
// without messing with prototypes.
// A Class that extends Function so we can create
// objects that also behave like functions, i.e. callable objects.
class ExFunc extends Function {
constructor() {
// Here we create a dynamic function with `super`,
// which calls the constructor of the parent class, `Function`.
// The dynamic function simply passes any calls onto
// an overridable object method which I named `__call__`.
// But there is a problem, the dynamic function created from
// the strings sent to `super` doesn't have any reference to `this`;
// our new object. There are in fact two `this` objects; the outer
// one being created by our class inside `constructor` and an inner
// one created by `super` for the dynamic function.
// So the reference to this in the text: `return this.__call__(...args)`
// does not refer to `this` inside `constructor`.
// So attempting:
// `obj = new ExFunc();`
// `obj();`
// Will throw an Error because __call__ doesn't exist to the dynamic function.
super('...args', 'return this.__call__(...args)');
// `bind` is the simple remedy to this reference problem.
// Because the outer `this` is also a function we can call `bind` on it
// and set a new inner `this` reference. So we bind the inner `this`
// of our dynamic function to point to the outer `this` of our object.
// Now our dynamic function can access all the members of our new object.
// So attempting:
// `obj = new Exfunc();`
// `obj();`
// Will work.
// We return the freshly bound `this`.
return this.bind(this);
}
// An example property to demonstrate member access.
get venture() {
return 'Hank';
}
// Override this method in subclasses of ExFunc to take whatever arguments
// you want and perform whatever logic you like. It will be called whenever
// you use the obj as a function.
__call__(a, b, c) {
return [this.venture, a, b, c];
}
}
// A subclass of ExFunc with an overridden __call__ method.
class DaFunc extends ExFunc {
get venture() {
return 'Dean';
}
__call__(ans) {
return [this.venture, ans];
}
}
// Create objects from ExFunc and its subclass.
var callable1 = new ExFunc();
var callable2 = new DaFunc();
// Inheritance is correctly maintained.
console.log('\nInheritance maintained:');
console.log(callable2 instanceof Function); // true
console.log(callable2 instanceof ExFunc); // true
console.log(callable2 instanceof DaFunc); // true
// Test ExFunc and its subclass objects by calling them like functions.
console.log('\nCallable objects:');
console.log( callable1(1, 2, 3) ); // [ 'Hank', 1, 2, 3 ]
console.log( callable2(42) ); // [ 'Dean', 42 ]
@SystemParadox
Copy link

Note to self, this also doesn't quite work - this.bind(this) returns a different object to the this that gets bound for the function. It looks right inside the class, but if you set something on the created instance no change is visible:

class Callable extends Function {
    constructor() {
        super('...args', 'return this.__call__(...args)');
        return this.bind(this);
    }

    __call__() {
        return this.x;
    }
}
const foo = new Callable();
foo.x = 4;
console.log(foo()); // undefined, should be 4

Possible solution:

class Callable extends Function {
    constructor() {
        super('...args', 'return this._this.__call__(...args)');
        const x = this.bind(this);
        this._this = x;
        return x;
    }

    __call__() {
        return this.x;
    }
}
const foo = new Callable();
foo.x = 4;
console.log(foo()); // 4 as expected

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment