Можете ли вы создавать функции с пользовательскими прототипами в JavaScript?

Прежде всего, я не хочу добавлять методы к Function.prototype. Это сделает их доступными для всех функций, и это не то, что я ищу.

В JavaScript вы можете создавать объекты с настраиваемыми прототипами, например:

function CustomObj() {}
CustomObj.prototype = {};
CustomObj.prototype.sayFoo = function () { return 'foo' };

var myCustomObj = new CustomObj(); //=> returns an object: {}
myCusomObj.sayFoo(); //=> 'foo'

Вы также можете создавать подобные массиву объекты с помощью собственных прототипов:

function CustomArr() {}
CustomArr.prototype = [];
CustomObj.prototype.sayFoo = function () { return 'foo' };

var myCustomArr = new CustomArr(); //=> returns an ordered object: []
myCustomArr.sayFoo(); //=> 'foo'

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

function CustomFn() {}
CustomFn.prototype = function () {};
CustomFn.prototype.sayFoo = function () { return 'foo' };

var myCustomFn = new CustomFn(); //=> PROBLEM! returns an object: {}
myCustomFn.sayFoo(); //=> 'foo'

// ^^ Here, the prototype was applied but the output was not a function.
myCustomFn(); //=> TypeError: object is not a function

Итак, есть ли способ сделать то, что я пытаюсь сделать?

UPDATE

Возможно, есть другой способ, которым я мог бы задать этот вопрос, который сделает его немного яснее.

Проблема с идеей замыкания:

function makeFn() {
  var output = function () { /* do some stuff */ };
  output.foo = function () { /* do some stuff */ };
  return output;
}
var specialFn = makeFn();

По сути, эта техника дает мне то, что я хочу. Однако проблема заключается в том, что каждый раз, когда я вызываю makeFn, output.foo должен быть создан как полностью независимая функция, которая берет свою собственную память. Валовой. Поэтому я мог бы вытащить этот метод из закрытия:

var protoMethods = {
  "foo" : function () { /* do some stuff */ }
};
function makeFn() {
  var output = function () { /* do some stuff */ };
  for (var i in protoMethods) {
    Object.prototype.hasOwnProperty.call(protoMethods, i) &&
      (output[i] = protoMethods[i]);
  }
  return output;
}
var specialFn = makeFn();

Но теперь я должен вручную выполнять итерацию каждый раз, когда я вызываю makeFn, что будет менее эффективным, чем если бы я мог просто назначить protoMethods прототипом output. Итак, с этим новым обновлением, любые идеи?

Ответ 1

Это сложная вещь, более сложная, чем должно быть, если язык был хорошо разработан...

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

В будущих версиях Javascript вы можете сделать это с помощью объекта "прокси", который может определить обработчик "вызов". Но это все еще слишком сложно и надуманно, на мой взгляд.

Другой способ сделать это - сделать ваш объект реальной функцией, а не обычным объектом. Затем попробуйте установить __proto__, который не является стандартным, но работает в большинстве современных браузеров, кроме Opera и IE 8 или меньше. Также возможно установить его свойство constructor для фальсификации instanceof проверок... такие хаки довольно сложны, и результаты будут сильно отличаться от сред.

Следующий пример отлично работает на моем Firefox: http://jsfiddle.net/Q3422/2/

function MyFun() {
    if (!this || this==window) {
        return new MyFun();
    }

    var f = function() {
        return "thanks for calling!";
    }
    f.__proto__ = MyFun.prototype;
    f.constructor = MyFun;

    return f;
}

MyFun.prototype = {
    foo: function() {
        return "foo:" + this();
    },
    __proto__: Function.prototype
};

var f = new MyFun();
alert("proto method:"+f.foo()); // try our prototype methods
alert("function method:"+f.call()); // try standard function methods
alert("function call:"+f()); // try use as a function
alert('typeof:' + typeof f); // "function", not "object". No way around it in current js versions
alert('is MyFun:' + (f instanceof MyFun)); // true
alert('is Function:' + (f instanceof Function)); // true

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

Итерация над прототипом для его копирования также не должна вас беспокоить, я думаю, у вас не будет gazillion методов.

Итак, ваше собственное последнее решение, вероятно, лучше всего, если вам нужно поддерживать среды, в которых proto не настраивается, и вы не беспокоитесь о том, что ваш прототип может быть расширен после того, как некоторые объекты уже созданы, и они может не поднять изменения.

Ответ 2

Я тоже пробовал это, работая над V library. Я хотел бы переопределить конструктор Function для обеспечения ограниченного синтаксиса функций-конструкторов, который я называю "функциями класса" (и я уверен в этом).

Ответ - нет, с помощью нового оператора вы можете создавать только новые "объекты", но не новые "функциональные объекты".

Однако вы можете использовать конструктор как конструктор, так и как функцию!

var CustomFn = function () {
  if (this instanceof CustomFn) {
    // here we are 'new CustomFn()'
  }
  else {
    // here we are 'CustomFn()' or 'CustomFn.call()'
  }
};

Или, как я считаю, это лучшее понятие, чтобы выполнить функцию на первом месте, а затем позволить конструктору:

var CustomFn = function () {
  if (!(this instanceof CustomFn)) { // functioning
    // here we are 'CustomFn()' or 'CustomFn.call()'
    return new CustomFn(); // or return undefined or throw
  }

  // constructing
  // here we are 'new CustomFn()'

  // BaseCustomFn.call(this);
};

Ответ 3

Вы находитесь в основе того, что такое наследование в JavaScript. Да, поскольку прототипы являются объектами, вы хотите установить прототип CustomFn для объекта вместо функции.

Но этот объект может исходить из другой функции:

function ParentFn() {}
function CustomFn() {}
CustomFn.prototype = Object.create(ParentFn.prototype);
CustomFn.prototype.sayFoo = fun ...

Если у вас нет ES5 или polyfill:

CustomFn.prototype = (function() {
                          function F(){}
                          F.prototype = ParentFn.prototype;
                          return new F();
                      }());

Некоторые из вас могут сказать вам просто сделать следующее, но лучше всего:

CustomFn.prototype = new ParentFn();