How do I make a callable JS object with an arbitrary prototype? [duplicate]

Possible Duplicate:
Can a JavaScript object have a prototype chain, but also be a function?

I'm looking to make a callable JavaScript object, with an arbitrary prototype chain, but without modifying Function.prototype.

In other words, this has to work:

var o = { x: 5 };
var foo = bar(o);
assert(foo() === "Hello World!");
delete foo.x;
assert(foo.x === 5);

Without making any globally changes.

Answers:

Answer

There's nothing to stop you from adding arbitrary properties to a function, eg.

function bar(o) {
    var f = function() { return "Hello World!"; }
    o.__proto__ = f.__proto__;
    f.__proto__ = o;
    return f;
}

var o = { x: 5 };
var foo = bar(o);
assert(foo() === "Hello World!");
delete foo.x;
assert(foo.x === 5);

I believe that should do what you want.

This works by injecting the object o into the prototype chain, however there are a few things to note:

  • I don't know if IE supports __proto__, or even has an equivalent, frome some's comments this looks to only work in firefox and safari based browsers (so camino, chrome, etc work as well).
  • o.__proto__ = f.__proto__; is only really necessary for function prototype functions like function.toString, so you might want to just skip it, especially if you expect o to have a meaningful prototype.
Answer

I'm looking to make a callable JavaScript object, with an arbitrary prototype chain, but without modifying Function.prototype.

I don't think there's a portable way to do this:

You must either set a function object's [[Prototype]] property or add a [[Call]] property to a regular object. The first one can be done via the non-standard __proto__ property (see olliej's answer), the second one is impossible as far as I know.

The [[Prototype]] can only portably be set during object creation via a constructor function's prototype property. Unfortunately, as far as I know there's no JavaScript implementation which would allow to temporarily reassign Function.prototype.

Answer

The closest cross browser thing I have come is this (tested in FF, IE, Crome and Opera):

function create(fun,proto){
    var f=function(){};
    //Copy the object since it is going to be changed.
    for (var x in proto)
        f.prototype[x] = proto[x];
    f.prototype.toString = fun;
    return new f;
}
var fun=function(){return "Hello world";}
var obj={x:5}

var foo=create(fun,obj);
foo.x=8;
alert(foo); //Hello world
alert(foo.x); // 8
delete foo.x;
alert(foo.x); // 5
Answer

You cannot do it in a portable way. However if you think about it, if the purpose of delete foo.x; is to reset the value of x, you could provide a reset() method on foo that will restore missing properties to their default values.

// Object creation and initialisation
(foo=function()
{
    alert("called");
}).reset = function()
{
    if(!("x"in this)) this.x=42;
};
foo.reset();

// Using our callable object
                           alert(foo.x); // 42
foo();                     alert(foo.x); // called; 42
foo.x=3;      foo.reset(); alert(foo.x); // 3 [*]
delete foo.x; foo.reset(); alert(foo.x); // 42

(Tested in Chromium and Internet Explorer, but this should work in all browsers.)

In the line marked with [*] the call to reset is really not necessary, but it's there to demonstrate that it doesn't matter if you call it accidentally, and that this generalises to more than one property easily.

Note that in the function body of our callable object this will refer to the containing scope, which will probably not be that useful to us since we'll want the function body to access the object members. To mitigate this, wrap it in a closure like this:

foo = (function()
{
    var self = function()
    {
        self.x = 42;
    };
    return self;
})();
foo(); alert(foo.x); // 42

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.