views:

487

answers:

3

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.

+2  A: 

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.
olliej
You have a ! after hello world in the assert, so it is false ;)
some
Doesn't work in IE or Opera 9.61
some
Bah, i should have tested, but i just copied the assert from the original question
olliej
It really doesn't work in opera? O_o
olliej
At least not in 9.61, there __proto__ is undefined.
some
Am hunting down an opera dev
olliej
Although Chrome is a Safari-based browser in the sense that it uses WebKit to render HTML, the JavaScript VM, V8, is unique to Chrome. Please correct this in your answer so I can accept it?
Andrey Fedorov
+1  A: 

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
some
Unfortunately i think that will be modifying the global function prototype :-/
olliej
@Olliej: Why? I'm creating a new empty function and modifying its prototype, not the global Function prototype.
some
Ah, yes, you are right, my bad -- however new f is creating an object, not a function that may be called.eg. create(fun,obj)() won't do what was asked.I suspect what the question asks for isn't possible in IE/Opera.
olliej
@Olliej: Correct, thats why I begin with "The closest cross browser thing I have come..." ;) I too suspect that it can't be done IE/Opera.
some
+1  A: 

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.

Christoph