11

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

}
20
  • 5
    What's wrong with taking advantage of prototype inheritance? It's just another scheme for polymorphism. Commented Jul 25, 2013 at 19:25
  • 2
    It is cross-browser, actually (though IE and [older versions of?] Opera don't seem to support it), and is in the ECMAScript ed6 spec. draft, so at some point it will be standard (unless they remove it from the specification in the future). You're right that it isn't standard at the moment, though. Commented Jul 26, 2013 at 13:25
  • 2
    What's your motivation for avoiding the module pattern? Seeing how obviously Javascript doesn't really have callable objects and seems to go on about this aspect of language design in the opposite way compared to Python Commented Jul 26, 2013 at 19:06
  • 1
    Note that .__proto__ is the same as .constructor.prototype Commented Jul 26, 2013 at 19:06
  • 1
    @SimonBoudrias Only when getting .__proto__, not when setting it. Setting the constructor's prototype after the fact has no effect on the already-created object, to change the prototype used by created objects you have to set those directly. And while you could probably remove all the properties in .constructor.prototype and then add new ones, that would mean the change in prototype properties would affect all objects created by that constructor, not just the given function (which can be desirable, of course, but may not be in this situation). Commented Jul 26, 2013 at 19:49

1 Answer 1

7

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


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__.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.