bashaus
bashaus

Reputation: 1673

Handle function calls on a class in Node.JS

Assuming that you have a class

class MyClass {
    world() {
        console.log("hello world");
    }
}

I can run the method similar to the following:

var hello = new MyClass();
hello.world();
# outputs: hello world

Is there a way to handle direct function calls on an object? For example:

hello();

Returns: TypeError: hello is not a function.

Can I make this call a default function? For example, similar to PHP's invoke function ...

Upvotes: 2

Views: 1883

Answers (1)

Pedro Castilho
Pedro Castilho

Reputation: 10512

We can only make something callable in JavaScript if that thing is an object which, at some point, delegates to Function.prototype. Therefore, our class will need to extend Function or extend from a class which extends Function. We also need to be able to access instance variables from our class object (in order to call invoke()), so it needs to be bound to itself. This binding can only happen in the constructor.

Since our class will inherit from Function, we need to call super before being able to use this . However, the Function constructor actually takes a code string, which we won't have, because we want to be able to set invoke later on. So we'll need to extend Function in a different class which will be the parent class to our class and which will do the work of setting the prototype of our dummy function (which we need in order to be able to call the returned object). Bringing all of this together, we get:

class ExtensibleFunction extends Function { 
  constructor(f) {                          
    // our link to Function is what makes this callable,
    // however, we want to be able to access the methods from our class
    // so we need to set the prototype to our class's prototype.
    return Object.setPrototypeOf(f, new.target.prototype);
  }
}

class MyClass extends ExtensibleFunction {
  constructor() {
    // we build an ExtensibleFunction which accesses 
    // the late-bound invoke method
    super(function() { return this.invoke(); });
    return this.bind(this); // and bind our instance 
                            // so we have access to instance values.
  }

  invoke() {
    console.log("Hello, world!");
  }
}

x = new MyClass();
x(); //prints "Hello, world!"

I mostly adapted the techniques found in this answer in order to do this.

An interesting aspect of using this technique is that you could name MyClass something like Callable and remove the invoke method - then any class which extends Callable would become callable as long as it had an invoke() method. In fact...

class ExtensibleFunction extends Function { 
  constructor(f) {                          
    // our link to Function is what makes this callable,
    // however, we want to be able to access the methods from our class
    // so we need to set the prototype to our class's prototype.
    return Object.setPrototypeOf(f, new.target.prototype);
  }
}

class Callable extends ExtensibleFunction {
  constructor() {
    // we build an ExtensibleFunction which accesses 
    // the late-bound invoke method
    super(function() { return this.invoke(); });
    return this.bind(this); // and bind our instance 
                            // so we have access to instance values.
  }
}

class CallableHello extends Callable {
  invoke() {
    console.log("Hello, world!");
  }
}

class CallableBye extends Callable {
  invoke() {
    console.log("Goodbye cruel world!");
  }
}

x = new CallableHello();
x(); //prints "Hello, world!"

y = new CallableBye();
y(); //prints "Goodbye cruel world!"

(Of course, you could get the same effect by setting properties on function objects, but this is more consistent I guess)

Upvotes: 3

Related Questions