¿Construcción dinámica de objetos en javascript?
dynamic-languages (4)
Cuando quiero llamar a una función en javascript con argumentos proporcionados desde otro lugar, puedo usar el método de apply
de la función como:
array = ["arg1", 5, "arg3"]
...
someFunc.apply(null, array);
pero ¿qué pasa si necesito llamar a un constructor de una manera similar? Parece que esto no funciona:
array = ["arg1", 5, "arg3"]
...
someConstructor.apply({}, array);
al menos no como estoy intentando
template = [''string1'', string2, ''etc''];
var resultTpl = Ext.XTemplate.apply({}, template);
esto no funciona marchito
Ext.XTemplate.prototype.constructor.apply({}, template);
¿Alguna forma de hacer que uno funcione? (En este caso particular, encontré que funcionará la new Ext.XTemplate(template)
, pero estoy interesado en el caso general)
Pregunta similar pero específica para los tipos incorporados y sin una respuesta que puedo usar: Crear una instancia de un objeto de JavaScript llamando a prototype.constructor.apply
Gracias.
Editar:
El tiempo ha pasado y ES6 y los transpilers ahora son una cosa. En ES6 es trivial hacer lo que quería: new someConstructor(...array)
. Babel lo convertirá en ES5 new (Function.prototype.bind.apply(someConstructor, [null].concat(array)))();
¿Qué se explica en Cómo construir un objeto JavaScript (usando ''aplicar'')? .
Dado que la pregunta original es bastante antigua, aquí hay métodos más simples en prácticamente cualquier navegador moderno (desde la escritura, 2018, estoy contando Chrome, FF, IE11, Edge ...).
var template = [''string1'', string2, ''etc''];
var resultTpl = Object.create(Ext.XTemplate.prototype);
Ext.XTemplate.apply(resultTpl, template);
Esas dos líneas también explican cómo funciona básicamente el new
operador.
Desde developer.mozilla : Las funciones enlazadas se adaptan automáticamente al uso con el nuevo operador para construir nuevas instancias creadas por la función objetivo. Cuando se usa una función enlazada para construir un valor, siempre que esto se ignore. Sin embargo, los argumentos proporcionados todavía se añaden a la llamada del constructor.
Dicho esto, todavía tenemos que usar aplicar para obtener los argumentos de una matriz y entrar en la llamada de enlace. Además, también debemos restablecer la función de enlace como el argumento de este de la función de aplicación. Esto nos da una línea muy breve que hace exactamente lo que se necesita.
function constructorApply(ctor, args){
return new (ctor.bind.apply(ctor, [null].concat(args)))();
};
No hay una manera simple y directa de hacer esto con una función de constructor. Esto se debe a que ocurren cosas especiales cuando se usa la new
palabra clave para llamar a una función de constructor, por lo que si no va a hacer eso, tiene que emular todas esas cosas especiales. Son:
- Creando una nueva instancia de objeto (lo estás haciendo).
- Configuración del prototipo interno de ese objeto en la propiedad
prototype
la función constructora. - Estableciendo la propiedad del
constructor
ese objeto. - Llamar a la función constructora con esa instancia de objeto como el valor de
this
(lo está haciendo). - Manejo del valor de retorno especial de la función constructora.
Creo que eso es todo, pero vale la pena volver a comprobar en la especificación .
Así que si puedes evitarlo y simplemente usar la función de constructor directamente, lo haría. :-) Sin embargo, si no puedes, puedes hacerlo, es incómodo e implica soluciones. (Vea también esta respuesta relacionada aquí en , aunque también cubro todo el terreno [y luego algunos]).
Su mayor problema es el # 2 arriba: Configuración del prototipo interno del objeto. Durante mucho tiempo, no había una forma estándar de hacer esto. Algunos navegadores __proto__
una propiedad __proto__
que lo hizo, así que puedes usarlo si está allí. La buena noticia es que ECMAScript 5 introduce una forma de hacerlo explícitamente: Object.create
. Así que los navegadores de vanguardia como Chrome tendrán eso. Pero si estás tratando con un navegador que no tiene Object.create
ni __proto__
, se pone un poco feo:
1) Definir una función constructora personalizada.
2) Establezca su propiedad prototype
en la propiedad prototype
de la función de constructor real
3) Úsalo para crear una instancia de objeto en blanco.
Que maneja el prototipo por ti. Entonces continúas con:
4) Reemplace la propiedad del constructor
en esa instancia con la función de constructor real.
5) Llame a la función de constructor real a través de apply
.
6) Si el valor de retorno de la función constructora real es un objeto, utilícelo en lugar del que creó; de lo contrario, usa el que creaste.
Algo como esto ( ejemplo en vivo ):
function applyConstruct(ctor, params) {
var obj, newobj;
// Use a fake constructor function with the target constructor''s
// `prototype` property to create the object with the right prototype
function fakeCtor() {
}
fakeCtor.prototype = ctor.prototype;
obj = new fakeCtor();
// Set the object''s `constructor`
obj.constructor = ctor;
// Call the constructor function
newobj = ctor.apply(obj, params);
// Use the returned object if there is one.
// Note that we handle the funky edge case of the `Function` constructor,
// thanks to Mike''s comment below. Double-checked the spec, that should be
// the lot.
if (newobj !== null
&& (typeof newobj === "object" || typeof newobj === "function")
) {
obj = newobj;
}
// Done
return obj;
}
Podría Object.create
un paso más allá y solo usar el constructor falso si es necesario, para ver si Object.create
o __proto__
son compatibles primero, de esta manera ( ejemplo en vivo ):
function applyConstruct(ctor, params) {
var obj, newobj;
// Create the object with the desired prototype
if (typeof Object.create === "function") {
// ECMAScript 5
obj = Object.create(ctor.prototype);
}
else if ({}.__proto__) {
// Non-standard __proto__, supported by some browsers
obj = {};
obj.__proto__ = ctor.prototype;
if (obj.__proto__ !== ctor.prototype) {
// Setting it didn''t work
obj = makeObjectWithFakeCtor();
}
}
else {
// Fallback
obj = makeObjectWithFakeCtor();
}
// Set the object''s constructor
obj.constructor = ctor;
// Apply the constructor function
newobj = ctor.apply(obj, params);
// If a constructor function returns an object, that
// becomes the return value of `new`, so we handle
// that here.
if (typeof newobj === "object") {
obj = newobj;
}
// Done!
return obj;
// Subroutine for building objects with specific prototypes
function makeObjectWithFakeCtor() {
function fakeCtor() {
}
fakeCtor.prototype = ctor.prototype;
return new fakeCtor();
}
}
En Chrome 6, lo anterior usa Object.create
; en Firefox 3.6 y Opera, usa __proto__
. En IE8, utiliza la función de constructor falso.
Lo anterior es bastante simple, pero principalmente trata los problemas que conozco en esta área.
Pienso que otra forma de lograr esto podría ser extender la clase Ext Template para que el constructor del nuevo objeto tome su matriz y haga las cosas que quiere hacer. De esta manera, todo el material de construcción se haría por usted y podría simplemente llamar al constructor de su superclase con sus argumentos.