¿Cómo puedo construir un objeto utilizando una matriz de valores para parámetros, en lugar de enumerarlos, en JavaScript?

StackOverflow https://stackoverflow.com/questions/813383

Pregunta

¿Es esto posible? Estoy creando una función de fábrica de una sola base para manejar fábricas de diferentes tipos (pero tengo algunas similitudes) y quiero poder pasar argumentos como una matriz a la fábrica de bases, lo que posiblemente crea una instancia de un nuevo objeto que llena los argumentos de El constructor de la clase relevante a través de una matriz.

En JavaScript es posible usar una matriz para llamar a una función con múltiples argumentos usando el método de aplicación:

namespace.myFunc = function(arg1, arg2) { //do something; }
var result = namespace.myFunc("arg1","arg2");
//this is the same as above:
var r = [ "arg1","arg2" ];
var result = myFunc.apply(namespace, r);

No parece como si de todos modos se creara una instancia de un objeto usando la aplicación, ¿verdad?

Algo como (esto no funciona):

var instance = new MyClass.apply(namespace, r);
¿Fue útil?

Solución

Prueba esto:

var instance = {};
MyClass.apply( instance, r);

Todas las palabras clave " nueva " Pasa un nuevo objeto al constructor, que luego se convierte en la variable dentro de la función del constructor.

Dependiendo de cómo se escribió el constructor, es posible que tengas que hacer esto:

var instance = {};
var returned = MyClass.apply( instance, args);
if( returned != null) {
    instance = returned;
}

Actualización: un comentario dice que esto no funciona si hay un prototipo. Prueba esto.

function newApply(class, args) {
    function F() {
        return class.apply(this, args);
    }
    F.prototype = class.prototype;
    return new F();
}

newApply( MyClass, args);

Otros consejos

Ten en cuenta que

  • new myClass()
    

    sin ningún argumento puede fallar, ya que la función constructora puede depender de la existencia de argumentos.

  • myClass.apply(something, args)
    

    fallará en muchos casos, especialmente si se llama en clases nativas como Fecha o Número.

Sé que " eval es malo " ;, pero en este caso es posible que desees probar lo siguiente:

function newApply(Cls, args) {
    var argsWrapper = [];
    for (var i = 0; i < args.length; i++) {
        argsWrapper.push('args[' + i + ']');
    }
    eval('var inst = new Cls(' + argsWrapper.join(',') + ');' );
    return inst;
}

Tan simple como eso.

(Funciona igual que Instance.New en esta publicación de blog )

Los hacks son hacks son hacks, pero quizás este sea un poco más elegante que algunos de los demás, ya que la sintaxis de llamadas sería similar a la que deseas y no necesitarías modificar las clases originales en absoluto:

Function.prototype.build = function(parameterArray) {
    var functionNameResults = (/function (.{1,})\(/).exec(this.toString());
    var constructorName = (functionNameResults && functionNameResults.length > 1) ? functionNameResults[1] : "";
    var builtObject = null;
    if(constructorName != "") {
       var parameterNameValues = {}, parameterNames = [];
       for(var i = 0; i < parameterArray.length; i++) {
         var parameterName = ("p_" + i);
         parameterNameValues[parameterName] = parameterArray[i];
         parameterNames.push(("parameterNameValues." + parameterName));
       }
       builtObject = (new Function("parameterNameValues", "return new " + constructorName + "(" + parameterNames.join(",") + ");"))(parameterNameValues);
    }
    return builtObject;
};

Ahora puedes hacer cualquiera de estos para construir un objeto:

var instance1 = MyClass.build(["arg1","arg2"]);
var instance2 = new MyClass("arg1","arg2");

Por supuesto, es posible que a algunos no les guste modificar el prototipo del objeto Function, por lo que puede hacerlo de esta manera y usarlo como una función:

function build(constructorFunction, parameterArray) {
    var functionNameResults = (/function (.{1,})\(/).exec(constructorFunction.toString());
    var constructorName = (functionNameResults && functionNameResults.length > 1) ? functionNameResults[1] : "";
    var builtObject = null;
    if(constructorName != "") {
       var parameterNameValues = {}, parameterNames = [];
       for(var i = 0; i < parameterArray.length; i++) {
         var parameterName = ("p_" + i);
         parameterNameValues[parameterName] = parameterArray[i];
         parameterNames.push(("parameterNameValues." + parameterName));
       }
       builtObject = (new Function("parameterNameValues", "return new " + constructorName + "(" + parameterNames.join(",") + ");"))(parameterNameValues);
    }
    return builtObject;
};

Y luego lo llamarías así:

var instance1 = build(MyClass, ["arg1","arg2"]);

Por lo tanto, espero que sean útiles para alguien; le permiten dejar las funciones originales del constructor y obtener lo que está buscando en una simple línea de código (a diferencia de las dos líneas que necesita para la solución / solución seleccionada actualmente) .

Los comentarios son bienvenidos y apreciados.


ACTUALIZACIÓN: Otra cosa a tener en cuenta: intente crear instancias del mismo tipo con estos métodos diferentes y luego verifique si las propiedades de su constructor son las mismas. Tal vez quiera que ese sea el caso si alguna vez necesita verificar el tipo de un objeto. Lo que quiero decir se ilustra mejor con el siguiente código:

function Person(firstName, lastName) {
   this.FirstName = firstName;
   this.LastName = lastName;
}

var p1 = new Person("John", "Doe");
var p2 = Person.build(["Sara", "Lee"]);

var areSameType = (p1.constructor == p2.constructor);

Intenta eso con algunos de los otros hacks y ve qué sucede. Lo ideal sería que fueran del mismo tipo.


CAVEAT: Como se señaló en los comentarios, esto no funcionará para aquellas funciones de constructor que se crean utilizando una sintaxis de función anónima, es decir,

MyNamespace.SomeClass = function() { /*...*/ };

A menos que los cree así:

MyNamespace.SomeClass = function SomeClass() { /*...*/ };

La solución que proporcioné anteriormente puede o no serle útil, debe comprender exactamente lo que está haciendo para llegar a la mejor solución para sus necesidades particulares, y debe tener conocimiento de lo que está por hacer. Mi solución "trabajo". Si no entiende cómo funciona mi solución, dedique tiempo a averiguarlo.


SOLUCIÓN ALTERNATIVA: no es una que pase por alto otras opciones, esta es una de las otras formas en que podría pelear a este gato (con advertencias similares al enfoque anterior), esta es un poco más esotérica:

function partial(func/*, 0..n args */) {
   var args = Array.prototype.slice.call(arguments, 1);
   return function() {
      var allArguments = args.concat(Array.prototype.slice.call(arguments));
      return func.apply(this, allArguments);
   };
}

Function.prototype.build = function(args) {
   var constructor = this;
   for(var i = 0; i < args.length; i++) {
      constructor = partial(constructor, args[i]);
   }
   constructor.prototype = this.prototype;
   var builtObject = new constructor();
   builtObject.constructor = this;
   return builtObject;
};

¡Disfruta!

¿qué pasa con una solución?

function MyClass(arg1, arg2) {

    this.init = function(arg1, arg2){
        //if(arg1 and arg2 not null) do stuff with args
    }

    init(arg1, arg2);
}

Entonces, cómo puedes:

var obj = new MyClass();
obj.apply(obj, args);

Una posibilidad es hacer que el constructor funcione como una llamada de función normal.

function MyClass(arg1, arg2) {
    if (!(this instanceof MyClass)) {
        return new MyClass(arg1, arg2);
    }

    // normal constructor here
}

La condición en la declaración si se cumplirá si llama a MyClass como una función normal (incluso con call / se aplica siempre que el argumento este no sea un MyClass object ).

Ahora todos estos son equivalentes:

new MyClass(arg1, arg2);
MyClass(arg1, arg2);
MyClass.call(null, arg1, arg2);
MyClass.apply(null, [arg1, arg2]);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top