Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do JavaScript classes have a method equivalent to Python classes' __call__?

In Python, you can implement __call__() for a class such that calling an instance of the class itself executes the __call__() method.

class Example:
    def __call__(self):
        print("the __call__ method")

e = Example()
e() # "the __call__ method" 

Do JavaScript classes have an equivalent method?

Edit

A summary answer incorporating the discussion here:

  • Python and JavaScript objects are not similar enough for a true equivalent (prototype vs. class based, self, vs. this)
  • The API can be achieved however using proxy or modifying the prototype – and maybe using bind?
  • One should generally not do this: it is too far removed from the JS's structure and from the normal use of of JavaScript, potentially creating confusion among other JS devs, your future-self, and could result in idiosyncratic side-effects.
like image 765
conner.xyz Avatar asked May 18 '19 00:05

conner.xyz


3 Answers

I basically agree with @CertainPerformace that this isn't really something you would do in normal JS code. Having said that, proxies offer a lot of possibilities and you can create something that is surprisingly close to (on the surface) to Python's __call__().

For example:

class F extends Function{
    constructor(someID, arr, n){
        super()
        this.id = someID
        this.arr = arr
        this.n = n

        return new Proxy(this, {
            apply(target, thisArg, argumentsList) {
              return target.__call__(...argumentsList);
            }
        })
    }
    __call__(a){                  // simple mult functions
        return a * this.n
    }

    *[Symbol.iterator](){         // make it iterable for demo purposes
        yield *this.arr.map(this) // call itself in a map!
    }
}


let f = new F("FrankenFunction", [1, 2, 3, 4], 5)

// access instance variable
console.log("id:", f.id)

// call it 
console.log("calling with 100: ", f(100))

// use the iterator
// get multiples of calling this on arr
console.log([...f])

// change the __call__ function to power instead
F.prototype.__call__ = function(a){
    return  a ** this.n 
}
// change n to get squares:
f.n = 2

// call it again with new __call__
console.log("calling with 10:", f(10))  // 10**2
console.log([...f]) // or iterate

I really not sure if any of this is a good idea, but it's an interesting experiment.

like image 176
Mark Avatar answered Oct 16 '22 08:10

Mark


The only way to do this would be for the constructor to explicitly return a function, which can be called. (In Javascript, if you don't explicitly return inside a constructor, the newly created instance gets returned - but such an instance will be a plain object, not a function.)

class Example {
  constructor() {
    return function() {
      console.log('function running');
    }
  }
}

const e = new Example();
e();

But this would be really weird to do, and would not allow you to reference any of the properties on the prototype, or anything like that. Better to avoid it, or to make a plain function that returns a function:

const example = () => () => console.log('function running');
const e = example();
e();
like image 37
CertainPerformance Avatar answered Oct 16 '22 09:10

CertainPerformance


You can get this done, but in a rather weird way.

There isn't anything like __call__(), __add__() or __sub__() in JavaScript - JavaScript does not support operator overloading.

However if you really want to make objects callable, you can do it by giving a function a different prototype:

function createCallableObject(cls, fn) {
    // wrap the original function to avoid modifying it
    const wrapper = (...args) => fn(...args)
    // set the prototype of the wrapped function so we can call class methods
    Object.setPrototypeOf(wrapper, cls.prototype)
    return wrapper
}

class Example {
    method() { console.log('This is an instance of class Example') }
}
function example() { console.log('This is a function') }

const obj = createCallableObject(Example, example)
obj() // 'This is a function'
obj.method() // 'This is an instance of class Example'
console.log(obj.constructor) // 'class Example { ... }'
like image 38
R Xy Avatar answered Oct 16 '22 09:10

R Xy