Предупреждение: это длинный пост.
Давайте будем простыми. Я хочу избежать необходимости добавлять префикс оператора 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, несмотря на все неудачи, это довольно быстро.