16

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.

Community
  • 1
  • 1
Andrey Fedorov
  • 9,148
  • 20
  • 67
  • 99
  • The underlying mechanism is the same, but this question is slightly different (the example starting points are quite different) and the answer adds significant value. Nominated for reopening. – kanaka Oct 26 '13 at 18:30

4 Answers4

12

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
  • 35,755
  • 9
  • 58
  • 55
  • You have a ! after hello world in the assert, so it is false ;) – some Feb 14 '09 at 06:08
  • 1
    Doesn't work in IE or Opera 9.61 – some Feb 14 '09 at 06:12
  • Bah, i should have tested, but i just copied the assert from the original question – olliej Feb 14 '09 at 06:15
  • 1
    It really doesn't work in opera? O_o – olliej Feb 14 '09 at 06:16
  • At least not in 9.61, there __proto__ is undefined. – some Feb 14 '09 at 06:20
  • 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 Feb 23 '09 at 23:18
  • This really isn't a situation in which you should be mucking around with `__proto__`. `__proto__` is used to expose the internal `[[Prototype]]` property of the object. This property is used for the purposes of delegation and changing it significantly negatively impacts performance. By changing the internal `[[Prototype]]` property, you are forfeiting your right to inheritance (unless you only want to inherit from `Function` for some reason). – Tyler Crompton Aug 07 '17 at 03:31
3

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.

Community
  • 1
  • 1
Christoph
  • 164,997
  • 36
  • 182
  • 240
1

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
  • 48,070
  • 14
  • 77
  • 93
  • Unfortunately i think that will be modifying the global function prototype :-/ – olliej Feb 14 '09 at 06:21
  • @Olliej: Why? I'm creating a new empty function and modifying its prototype, not the global Function prototype. – some Feb 14 '09 at 06:35
  • 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 Feb 14 '09 at 06:40
  • @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 Feb 14 '09 at 06:46
0

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