Предупреждение: это длинный пост.
Давайте будем простыми. Я хочу избежать необходимости добавлять префикс оператора new каждый раз, когда я вызываю конструктор в JavaScript. Это потому, что я склонен забывать об этом, и мой код плохо работает.
Простой способ обойти это ...
function Make(x) {
if ( !(this instanceof arguments.callee) )
return new arguments.callee(x);
// do your stuff...
}
Но мне нужно это, чтобы принять переменную №. аргументов, как это ...
m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');
Первым немедленным решением, похоже, является метод apply:
function Make() {
if ( !(this instanceof arguments.callee) )
return new arguments.callee.apply(null, arguments);
// do your stuff
}
Однако это НЕПРАВИЛЬНО - новый объект передается apply
методу, а НЕ нашему конструктору arguments.callee
.
Теперь у меня есть три решения. Мой простой вопрос: какой из них кажется лучшим. Или, если у вас есть лучший метод, скажите это.
Первый - использовать eval()
для динамического создания кода JavaScript, который вызывает конструктор.
function Make(/* ... */) {
if ( !(this instanceof arguments.callee) ) {
// collect all the arguments
var arr = [];
for ( var i = 0; arguments[i]; i++ )
arr.push( 'arguments[' + i + ']' );
// create code
var code = 'new arguments.callee(' + arr.join(',') + ');';
// call it
return eval( code );
}
// do your stuff with variable arguments...
}
Второе - у каждого объекта есть __proto__
свойство, которое является «секретной» ссылкой на его объект-прототип. К счастью, это свойство доступно для записи.
function Make(/* ... */) {
var obj = {};
// do your stuff on 'obj' just like you'd do on 'this'
// use the variable arguments here
// now do the __proto__ magic
// by 'mutating' obj to make it a different object
obj.__proto__ = arguments.callee.prototype;
// must return obj
return obj;
}
Третье - это что-то похожее на второе решение.
function Make(/* ... */) {
// we'll set '_construct' outside
var obj = new arguments.callee._construct();
// now do your stuff on 'obj' just like you'd do on 'this'
// use the variable arguments here
// you have to return obj
return obj;
}
// now first set the _construct property to an empty function
Make._construct = function() {};
// and then mutate the prototype of _construct
Make._construct.prototype = Make.prototype;
eval
Решение кажется неуклюжим и идет со всеми проблемами «зла Эвал».__proto__
решение нестандартное, и «Великий браузер mIsERY» не соблюдает его.Третье решение кажется слишком сложным.
Но со всеми вышеупомянутыми тремя решениями мы можем сделать что-то подобное, что мы не можем иначе ...
m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');
m1 instanceof Make; // true
m2 instanceof Make; // true
m3 instanceof Make; // true
Make.prototype.fire = function() {
// ...
};
m1.fire();
m2.fire();
m3.fire();
Таким образом, вышеприведенные решения дают нам «истинные» конструкторы, которые принимают переменную no. аргументов и не требуютnew
. Что ты думаешь об этом?
-- ОБНОВИТЬ --
Кто-то сказал «просто выбросить ошибку». Мой ответ таков: мы создаем тяжелое приложение с более чем 10 конструкторами, и я думаю, что было бы гораздо более утомительно, если бы каждый конструктор мог «разумно» обработать эту ошибку, не выдавая сообщения об ошибках на консоли.
Make()
без, new
потому что Make
new
? Потому что, если это последнее, вы, вероятно, спрашиваете не на том сайте. Если это первое, вы, возможно, захотите не отклонять предложения относительно использования новых и быстрого обнаружения ошибок ... Если ваше приложение действительно "тяжелое", последнее, что вам нужно, - это какой-то перегруженный механизм конструирования, чтобы замедлить его. new
, несмотря на все неудачи, это довольно быстро.