javascript reflection

Crear una instancia de un objeto JavaScript llamando a prototype.constructor.apply



reflection (12)

Permítanme comenzar con un ejemplo específico de lo que estoy tratando de hacer.

Tengo una serie de componentes de año, mes, día, hora, minuto, segundo y milisegundo en el formulario [ 2008, 10, 8, 00, 16, 34, 254 ] . Me gustaría crear una instancia de un objeto Date usando el siguiente constructor estándar:

new Date(year, month, date [, hour, minute, second, millisecond ])

¿Cómo puedo pasar mi matriz a este constructor para obtener una nueva instancia de fecha? [ Actualización : mi pregunta en realidad se extiende más allá de este ejemplo específico. Me gustaría una solución general para clases incorporadas de JavaScript como Date, Array, RegExp, etc. cuyos constructores están fuera de mi alcance. ]

Estoy tratando de hacer algo como lo siguiente:

var comps = [ 2008, 10, 8, 00, 16, 34, 254 ]; var d = Date.prototype.constructor.apply(this, comps);

Probablemente necesito un " new " allí en alguna parte. Lo anterior simplemente devuelve la hora actual como si hubiera llamado " (new Date()).toString() ". También reconozco que puedo estar completamente en la dirección incorrecta con lo anterior :)

Nota : No eval() y no acceder a los elementos de la matriz uno por uno, por favor. Estoy bastante seguro de que debería ser capaz de usar la matriz como está.

Actualización: Experimentos adicionales

Como nadie ha podido encontrar una respuesta funcional, he jugado más. Aquí hay un nuevo descubrimiento.

Puedo hacer esto con mi propia clase:

function Foo(a, b) { this.a = a; this.b = b; this.toString = function () { return this.a + this.b; }; } var foo = new Foo(1, 2); Foo.prototype.constructor.apply(foo, [4, 8]); document.write(foo); // Returns 12 -- yay!

Pero no funciona con la clase de fecha intrínseca:

var d = new Date(); Date.prototype.constructor.call(d, 1000); document.write(d); // Still returns current time :(

Tampoco funciona con Number:

var n = new Number(42); Number.prototype.constructor.call(n, 666); document.write(n); // Returns 42

Tal vez esto simplemente no es posible con objetos intrínsecos? Estoy probando con Firefox BTW.


Apenas lo llamaría elegante, pero en mis pruebas (FF3, Saf4, IE8) funciona:

var arr = [ 2009, 6, 22, 10, 30, 9 ];

En lugar de esto:

var d = new Date( arr[0], arr[1], arr[2], arr[3], arr[4], arr[5] );

Prueba esto:

var d = new Date( Date.UTC.apply( window, arr ) + ( (new Date()).getTimezoneOffset() * 60000 ) );


Aquí hay otra solución:

function createInstance(Constructor, args){ var TempConstructor = function(){}; TempConstructor.prototype = Constructor.prototype; var instance = new TempConstructor; var ret = Constructor.apply(instance, args); return ret instanceof Object ? ret : instance; } console.log( createInstance(Date, [2008, 10, 8, 00, 16, 34, 254]) )


Así es como lo haces:

function applyToConstructor(constructor, argArray) { var args = [null].concat(argArray); var factoryFunction = constructor.bind.apply(constructor, args); return new factoryFunction(); } var d = applyToConstructor(Date, [2008, 10, 8, 00, 16, 34, 254]);

Funcionará con cualquier constructor, no solo built-ins o constructores que puedan funcionar como funciones (como Date).

Sin embargo, requiere la función Ectascript 5 .bind. Las cuñas probablemente no funcionen correctamente.

Por cierto, una de las otras respuestas sugiere devolver this de un constructor. Eso puede hacer que sea muy difícil extender el objeto utilizando la herencia clásica, por lo que lo consideraría un antipatrón.


Así es como puedes resolver el caso específico:

function writeLn(s) { //your code to write a line to stdout WScript.Echo(s) } var a = [ 2008, 10, 8, 00, 16, 34, 254 ] var d = NewDate.apply(null, a) function NewDate(year, month, date, hour, minute, second, millisecond) { return new Date(year, month, date, hour, minute, second, millisecond); } writeLn(d)

Sin embargo, estás buscando una solución más general. El código recomendado para crear un método constructor es hacer que return this .

Por lo tanto:-

function Target(x , y) { this.x = x, this.y = y; return this; }

podría ser construido: -

var x = Target.apply({}, [1, 2]);

Sin embargo, no todas las implementaciones funcionan así porque la cadena de prototipos sería incorrecta:

var n = {}; Target.prototype = n; var x = Target.apply({}, [1, 2]); var b = n.isPrototypeOf(x); // returns false var y = new Target(3, 4); b = n.isPrototypeOf(y); // returns true


Editado

Lo siento, estaba seguro de que lo hice de esa manera hace años, ahora me quedaré con:

var d = new Date (comps [0], comps [1], comps [2], comps [3], comps [4], comps [5], comps [6]);

Editar:

Pero recuerde que un objeto de fecha de JavaScript usa índices durante meses, por lo que la matriz anterior significa

8 de noviembre de 2008 00: 16: 34: 254


Es menos que elegante, pero aquí hay una solución:

function GeneratedConstructor (methodName, argumentCount) { var params = [] for (var i = 0; i < argumentCount; i++) { params.push("arguments[" + i + "]") } var code = "return new " + methodName + "(" + params.join(",") + ")" var ctor = new Function(code) this.createObject = function (params) { return ctor.apply(this, params) } }

La forma en que esto funciona debería ser bastante obvia. Crea una función a través de la generación de código. Este ejemplo tiene un número fijo de parámetros para cada constructor que crees, pero de todos modos es útil. La mayoría de las veces tiene al menos un número máximo de argumentos en mente. Esto también es mejor que algunos de los otros ejemplos aquí porque le permite generar el código una vez y luego volver a usarlo. El código que se genera aprovecha la función de argumento variable de javascript, de esta manera puede evitar tener que nombrar cada parámetro (o deletrearlos en una lista y pasar los argumentos a la función que genera). Aquí hay un ejemplo de trabajo:

var dateConstructor = new GeneratedConstructor("Date", 3) dateConstructor.createObject( [ 1982, 03, 23 ] )

Esto devolverá lo siguiente:

Viernes 23 de abril de 1982 00:00:00 GMT-0800 (PST)

De hecho todavía es ... un poco feo. Pero al menos oculta cómodamente el problema y no supone que el código compilado en sí mismo pueda obtener basura recolectada (ya que eso puede depender de la implementación y es un área probable para errores).

Saludos, Scott S. McCoy


Funcionará con el operador de propagación ES6. Tu simplemente:

const arr = [2018, 6, 15, 12, 30, 30, 500]; const date = new Date(...arr); console.log(date);


He hecho más investigación propia y he llegado a la conclusión de que esto es una hazaña imposible , debido a cómo se implementa la clase Date.

He inspeccionado el código fuente de SpiderMonkey para ver cómo se implementó la fecha. Creo que todo se reduce a las siguientes líneas:

static JSBool Date(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) { jsdouble *date; JSString *str; jsdouble d; /* Date called as function. */ if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) { int64 us, ms, us2ms; jsdouble msec_time; /* NSPR 2.0 docs say ''We do not support PRMJ_NowMS and PRMJ_NowS'', * so compute ms from PRMJ_Now. */ us = PRMJ_Now(); JSLL_UI2L(us2ms, PRMJ_USEC_PER_MSEC); JSLL_DIV(ms, us, us2ms); JSLL_L2D(msec_time, ms); return date_format(cx, msec_time, FORMATSPEC_FULL, rval); } /* Date called as constructor. */ // ... (from here on it checks the arg count to decide how to create the date)

Cuando se utiliza la fecha como una función (ya sea como Date() o Date.prototype.constructor() , que son exactamente lo mismo), de forma predeterminada devuelve la hora actual como una cadena en el formato de configuración regional. Esto es independientemente de cualquier argumento que se transmita en:

alert(Date()); // Returns "Thu Oct 09 2008 23:15:54 ..." alert(typeof Date()); // Returns "string" alert(Date(42)); // Same thing, "Thu Oct 09 2008 23:15:54 ..." alert(Date(2008, 10, 10)); // Ditto alert(Date(null)); // Just doesn''t care

No creo que haya nada que se pueda hacer en el nivel JS para eludir esto. Y este es probablemente el final de mi búsqueda en este tema.

También noté algo interesante:

/* Set the value of the Date.prototype date to NaN */ proto_date = date_constructor(cx, proto); if (!proto_date) return NULL; *proto_date = *cx->runtime->jsNaN;

Date.prototype es una instancia de Date con el valor interno de NaN y, por lo tanto,

alert(Date.prototype); // Always returns "Invalid Date" // on Firefox, Opera, Safari, Chrome // but not Internet Explorer

IE no nos decepciona. Hace las cosas de forma un poco diferente y probablemente establece el valor interno en -1 para que Date.prototype siempre devuelva una fecha ligeramente antes de la época.

Actualizar

Finalmente me he metido en el ECMA-262 y resulta que lo que estoy tratando de lograr (con el objeto Date) es, por definición, imposible:

15.9.2 El constructor de fecha llamado como una función

Cuando se llama a Date como una función en lugar de como un constructor, devuelve una cadena que representa la hora actual (UTC).

NOTA La función llamada Date(…) no es equivalente a la expresión de creación del objeto new Date(…) con los mismos argumentos.

15.9.2.1 Fecha ([año [, mes [, fecha [, horas [, minutos [, segundos [, ms]]]]]]])

Todos los argumentos son opcionales; cualquier argumento proporcionado es aceptado pero se ignora por completo. Se crea una cadena y se devuelve como si fuera por la expresión (new Date()).toString() .


Puedes hacerlo con abuso flagrante y flagrante de eval:

var newwrapper = function (constr, args) { var argHolder = {"c": constr}; for (var i=0; i < args.length; i++) { argHolder["$" + i] = args[i]; } var newStr = "new (argHolder[''c''])("; for (var i=0; i < args.length; i++) { newStr += "argHolder[''$" + i + "'']"; if (i != args.length - 1) newStr += ", "; } newStr += ");"; return eval(newStr); }

uso de muestra:

function Point(x,y) { this.x = x; this.y = y; } var p = __new(Point, [10, 20]); alert(p.x); //10 alert(p instanceof Point); //true

disfrutar =).


Sé que ha pasado mucho tiempo, pero tengo la respuesta real a esta pregunta. Esto está lejos de ser imposible. Consulte https://gist.github.com/747650 para obtener una solución genérica.

var F = function(){}; F.prototype = Date.prototype; var d = new F(); Date.apply(d, comps);


function gettime() { var q = new Date; arguments.length && q.setTime( ( arguments.length === 1 ? typeof arguments[0] === ''number'' ? arguments[0] : Date.parse( arguments[0] ) : Date.UTC.apply( null, arguments ) ) + q.getTimezoneOffset() * 60000 ); return q; }; gettime(2003,8,16) gettime.apply(null,[2003,8,16])


var comps = [ 2008, 10, 8, 00, 16, 34, 254 ]; var d = eval("new Date(" + comps.join(",") + ");");