Можете ли вы создавать функции с пользовательскими прототипами в 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();