Advertencia: esta es una publicación larga.
Hagámoslo simple. Quiero evitar tener que prefijar el nuevo operador cada vez que llamo a un constructor en JavaScript. Esto se debe a que tiendo a olvidarlo, y mi código se arruina mal.
La forma simple de evitar esto es esto ...
function Make(x) {
if ( !(this instanceof arguments.callee) )
return new arguments.callee(x);
// do your stuff...
}
Pero, necesito esto para aceptar la variable no. de argumentos como este ...
m1 = Make();
m2 = Make(1,2,3);
m3 = Make('apple', 'banana');
La primera solución inmediata parece ser el método 'aplicar' como este ...
function Make() {
if ( !(this instanceof arguments.callee) )
return new arguments.callee.apply(null, arguments);
// do your stuff
}
Sin embargo, esto es INCORRECTO: el nuevo objeto se pasa al apply
método y NO a nuestro constructor arguments.callee
.
Ahora, he encontrado tres soluciones. Mi simple pregunta es: cuál parece mejor. O, si tiene un método mejor, dígalo.
Primero , use eval()
para crear dinámicamente código JavaScript que llame al constructor.
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...
}
Segundo : cada objeto tiene una __proto__
propiedad que es un enlace 'secreto' a su objeto prototipo. Afortunadamente, esta propiedad se puede escribir.
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;
}
Tercero : esto es algo similar a la segunda solución.
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
La solución parece torpe y viene con todos los problemas de "evaluación malvada".__proto__
la solución no es estándar y el "Gran navegador de MISERY" no la cumple.La tercera solución parece demasiado complicada.
Pero con las tres soluciones anteriores, podemos hacer algo como esto, de lo contrario no podemos ...
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();
Entonces, efectivamente, las soluciones anteriores nos dan constructores "verdaderos" que aceptan la variable no. de argumentos y no requieren new
. Cuál es su opinión sobre esto.
- ACTUALIZACIÓN -
Algunos han dicho "solo arroja un error". Mi respuesta es: estamos haciendo una aplicación pesada con más de 10 constructores y creo que sería mucho más manejable si cada constructor pudiera "inteligentemente" manejar ese error sin lanzar mensajes de error en la consola.
fuente
Make()
sinnew
Como make es capitalizado y por lo tanto se asume que es un constructornew
? Porque si es lo último, probablemente estés preguntando en el sitio equivocado. Si es lo primero, es posible que no desee descartar sugerencias sobre el uso de nuevos y la detección de errores tan rápido ... Si su aplicación es realmente "pesada", lo último que desea es algún mecanismo de construcción sobrecargado para ralentizarlo.new
, a pesar de todo el flack que recibe, es bastante rápido.Respuestas:
En primer lugar,
arguments.callee
está obsoleto en ES5 estricto, por lo que no lo usamos. La verdadera solución es bastante simple.No lo usas
new
en absoluto.Eso es un dolor en el culo ¿verdad?
Tratar
enhance
Ahora, por supuesto, esto requiere ES5, pero todos usan la cuña ES5 ¿verdad?
Usted también podría estar interesado en patrones alternativos de js OO
Como aparte, puede reemplazar la opción dos con
En caso de que quieras tu propia
Object.create
cuña ES5, entonces es realmente fácilfuente
Object.create
. ¿Qué pasa con pre ES5? ES5-ShimObject.create
aparece como DUBIOSO.__proto__
cosa allí, entonces todavía estamos en el mismo punto. Debido a que antes de ES5 NO hay una forma más fácil de mutar el prototipo. Pero de todos modos, su solución parece más elegante y prospectiva. +1 por eso. (se alcanza mi límite de votación)Object.create
cuña es más o menos mi tercera solución, pero menos complicada y más atractiva que la mía, por supuesto.La respuesta obvia sería no olvidar la
new
palabra clave.Estás cambiando la estructura y el significado del lenguaje.
Lo cual, en mi opinión, y por el bien de los futuros mantenedores de su código, es una idea horrible.
fuente
new
o no, la encontraría más fácil de mantener.;
declaraciones de finalización. (Inserción automática de punto y coma)new
o no es semánticamente idéntico . No hay ningún caso sutiles en el que se ha roto esta invariante. Por eso es bueno y por qué querrías usarlo.La solución más fácil es recordar
new
y lanzar un error para que sea obvio que lo olvidó.Hagas lo que hagas, no lo uses
eval
. Me gustaría evitar el uso de propiedades no estándar como__proto__
específicamente porque no son estándar y su funcionalidad puede cambiar.fuente
.__proto__
es el diabloDe hecho, escribí una publicación sobre esto. http://js-bits.blogspot.com/2010/08/constructors-without-using-new.html
E incluso puede generalizarlo para no tener que agregarlo a la parte superior de cada constructor. Puedes ver eso visitando mi publicación
Descargo de responsabilidad No uso esto en mi código, solo lo publiqué por el valor didáctico. Descubrí que olvidar a
new
es un error fácil de detectar. Como otros, no creo que realmente necesitemos esto para la mayoría del código. A menos que esté escribiendo una biblioteca para crear herencia JS, en cuyo caso podría usarla desde un solo lugar y ya estaría usando un enfoque diferente al de la herencia directa.fuente
var x = new Ctor();
y luego tengo x asthis
y dovar y = Ctor();
, esto no se comportaría como se esperaba.this
", ¿puede publicar un jsfiddle para mostrar el problema potencial?Ctor.call(ctorInstance, 'value')
. No veo un escenario válido para lo que estás haciendo. Para construir un objeto, usevar x = new Ctor(val)
ovar y=Ctor(val)
. Incluso si hubiera un escenario válido, mi afirmación es que puede tener constructores sin usarnew Ctor
, no que puede tener constructores que funcionen usandoCtor.call
Ver jsfiddle.net/JHNcR/2¿Qué tal esto?
EDITAR: Olvidé agregar:
"Si su aplicación es realmente 'pesada', lo último que desea es algún mecanismo de construcción sobrecargado para ralentizarla"
Estoy totalmente de acuerdo: al crear 'cosa' anterior sin la palabra clave 'nueva', es más lenta / más pesada que con ella. Los errores son tus amigos, porque te dicen lo que está mal. Además, le dicen a sus compañeros desarrolladores qué están haciendo mal.
fuente