Search code examples
javascriptfunctionpropertiesprototypeprototype-chain

How to invoke an object like a function in Javascript?


Suppose you have a function:

const myFunc = function () {
  console.log('Function has been invoked');
}

You are able to invoke a function by using parentheses as myFunc().

Now suppose you have an object. Even though functions are objects you cannot invoke an object with parentheses:

const myObj = {};
myObj(); // TypeError: myObj is not a function

So is there a way to make an object invokable like a function in JS?

First of all I thought about an internal method [[Call]] that function objects have. I've tried to set this property by myself but got an error:

const myObject = {
  [[Call]]: function() {
    console.log("My object was invoked!");
  }
};
// ReferenceError: Call is not defined

Then I read that there is no way to in EcmaScript to configure this property by yourself so there is no way to access and change this internal property of an object.

Next I thought about Proxy, which has apply() trap to catch the [[Call]] method of function objects. I've tried to set up proxy, but still got an error:

const callableObject = new Proxy({}, {
  apply: function() {
    console.log("Callable object was invoked!");
  }
});

callableObject(); // TypeError: callableObject is not a function

I've tried to change the prototype chain such that the prototype of an object is Function.prototype, but it still did not work:

const obj = {};
Object.setPrototypeOf(obj, Function.prototype)
const callableObject = new Proxy(obj, {
    apply: function() {
        console.log("Callable object was invoked!");
    }
});
  
callableObject(); // TypeError: callableObject is not a function

It seems that Proxy is capable of doing this but I still can't think of a way.

The only possibility I see is just to create a function in the first place, then treat it like an object, and then ivoke it when needed.


Solution

  • A function itself is an object:

    console.log(Object.getPrototypeOf(Function.prototype).constructor.name);
    
    const obj = function(){};
    obj.prop = 'test';
    
    console.log(obj.prop);

    So you could use a function directly as an object, but be careful about a function's props like name, length, etc.

    Since you are quite flexible about your syntax going beyond const obj = {} and allow using proxies you could proxy a function but keep the props separate in a dedicated object. So the proxied function would be the object's accessor:

    const obj = {};
    
    const callableObject = new Proxy(new Function, {
      apply() {
        console.log("Callable object was invoked!");
      },
      get(_, prop){
        return Reflect.get(obj, prop);
      },
      set(_, prop, val){
        Reflect.set(obj, prop, val);
      }
    });
    
    callableObject();
    
    callableObject.prop = 'test';
    
    console.log(callableObject.prop, obj.prop);