Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replicating python's __call__ in javascript?

I want to replicate instantiating a callable class without using the module pattern.

The following is my best attempt at this. However, it uses __proto__ which I'm not sure about. Can this be done without __proto__?

function classcallable(cls) {
    /*
     * Replicate the __call__ magic method of python and let class instances
     * be callable.
     */
    var new_cls = function () {
        var obj = Object.create(cls.prototype);
        // create callable
        // we use func.__call__ because call might be defined in
        // init which hasn't been called yet.
        var func = function () {
            return func.__call__.apply(func, arguments);
        };
        func.__proto__ = obj;
        // apply init late so it is bound to func and not cls
        cls.apply(func, arguments);
        return func;
    }
    new_cls.prototype = cls.prototype;
    return new_cls

}
like image 966
Dale Jung Avatar asked Jul 25 '13 19:07

Dale Jung


Video Answer


1 Answers

Proxy

If you are against using __proto__ or anything related to it but still want the inheritability, you could make use of Proxies.

var ObjectCallable_handler = {
    get: function get(self, key) {
        if (self.hasOwnProperty(key)) {
            return self[key];
        } else { return self.__inherit__[key]; }
    },
    apply: function apply(self, thisValue, args) {
        return (self.__call__ || self.__inherit__.__call__).apply(self, args);
    }
};

function ObjectCallable(cls) {
    var p = new Proxy(function() { }, ObjectCallable_handler);
    p.__inherit__ = cls;
    return p;
}

Pros

  • Maintains inheritability.
  • Does not involve a traditional prototype chain.
  • ES6 draft.

Cons

  • Currently supported only by Firefox >=24.

setPrototypeOf

If the lack of support for Proxies dismays you, you could try a setPrototypeOf polyfill instead.

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
    obj.__proto__ = proto;
    return obj;
}

function ObjectCallable(cls) {
    var f = function() { return f.__call__.apply(f, arguments); };
    Object.setPrototypeOf(f, cls);
    return f;
}

Pros

  • Likely to work the best right now and in the future.
  • ES6 draft.

Cons

  • Makes use of the non-standard __proto__ through the polyfill and will require testing across the engines/browsers you want to support.
  • setPrototypeOf is not currently implemented in any browser.

copy

This is the simplest solution with the least amount of functionality, but it's guaranteed to work almost anywhere. Create a new function and clone an object's properties onto it.

function ObjectCallable(cls) {
    var f = function() { return f.__call__.apply(f, arguments); }, k;
    for (k in cls) {
        f[k] = cls[k];
    }
    return f;
}

Pros

  • Works virtually everywhere.

Cons

  • Does not maintain any semblance of an inheritance or prototype structure whatsoever.

Conclusion

What you want to do with replicating Python's __call__ functionality in JavaScript will require either more time as ES6 develops and becomes implemented in more engines or relying on non-standard functionality such as __proto__.

like image 127
Ryan Stein Avatar answered Oct 06 '22 00:10

Ryan Stein