Как создать вызываемый JS-объект с произвольным прототипом?

Возможный дубликат:
Может ли объект JavaScript иметь цепочку прототипов, а также быть функцией?

Я хочу создать вызываемый JavaScript-объект с произвольной цепочкой прототипов, но без изменения Function.prototype.

Другими словами, это должно работать:

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

Без каких-либо глобальных изменений.

Ответы

Ответ 1

Вам нечего мешать добавлять какие-либо функции к функции, например.

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);

Я считаю, что нужно делать то, что вы хотите.

Это работает, введя объект o в цепочку прототипов, однако есть несколько замечаний:

  • Я не знаю, поддерживает ли IE __proto__ или даже имеет эквивалент, из некоторых комментариев это выглядит только для работы в браузерах firefox и сафари (так работают и camino, chrome и т.д.).
  • o.__proto__ = f.__proto__; действительно необходим только для функций прототипа функций, таких как function.toString, поэтому вы можете просто пропустить его, особенно если вы ожидаете, что o будет иметь значимый прототип.

Ответ 2

Я хочу создать вызываемый JavaScript-объект с произвольной цепочкой прототипов, но без изменения Function.prototype.

Я не думаю, что есть переносной способ сделать это:

Вы должны либо установить свойство объекта [[Prototype]], либо добавить свойство [[Call]] к обычным объектам. Первый из них можно выполнить с помощью нестандартного свойства __proto__ (см. olliej answer), второй, насколько мне известно, невозможно.

[[Прототип]] можно настраивать только при создании объекта с помощью функции конструктора prototype. К сожалению, насколько я знаю, нет реализации JavaScript, которая позволила бы временно переназначить Function.prototype.

Ответ 3

Ближайшая кросс-браузерная вещь, к которой я пришел, - это (протестировано в FF, IE, Crome и 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

Ответ 4

Вы не можете сделать это переносимым способом. Однако если вы думаете об этом, если целью delete foo.x; является reset значение x, вы можете предоставить метод reset() на foo, который восстановит отсутствующие свойства по умолчанию.

// 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

(Протестировано в Chromium и Internet Explorer, но это должно работать во всех браузерах.)

В строке, помеченной [*], вызов reset действительно не нужен, но он там, чтобы продемонстрировать, что это не имеет значения, если вы называете это случайно, и что это обобщает несколько свойств.

Обратите внимание, что в теле функции нашего вызываемого объекта this будет ссылаться на область содержимого, которая, вероятно, не будет такой полезной для нас, так как мы хотим, чтобы тело функции обращалось к членам объекта. Чтобы уменьшить это, оберните его в закрытие следующим образом:

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