Como posso construir um objeto usando uma matriz de valores para os parâmetros, ao invés de listá-los para fora, em JavaScript?

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

Pergunta

Isso é possível? Estou criando uma única função fábrica de base para dirigir fábricas de diferentes tipos (mas têm algumas semelhanças) e quero ser capaz de passar argumentos como uma matriz para a fábrica de base que, em seguida, possivelmente, cria uma instância de um novo objeto preencher os argumentos de o construtor da classe relevante através de uma matriz.

Em JavaScript é possível usar uma matriz para chamar uma função com vários argumentos usando a aplicar o método:

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);

Não parece como se houvesse uma maneira de criar uma instância de um objeto usando aplicam, porém, não é?

Algo como (Isto não funciona):

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

Solução

Tente isto:

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

Toda a palavra-chave "novo" faz é passar em um novo objeto para o construtor que então se torna a esta variável dentro da função de construtor.

Dependendo de como o construtor foi escrito, você pode ter que fazer isso:

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

Update: Um comentário diz que isso não funciona se houver um protótipo. Tente isto.

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

newApply( MyClass, args);

Outras dicas

Note que

  • new myClass()
    

    sem nenhum argumento pode falhar, uma vez que a função de construtor poderá contar com a existência de argumentos.

  • myClass.apply(something, args)
    

    falhará em muitos casos, especialmente se for chamado em classes nativas como Data ou número.

Eu sei que "eval é mau", mas neste caso, você pode querer tentar o seguinte:

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;
}

Simples como isso.

(Ele funciona da mesma forma Instance.New em este post )

Hacks são hacks são hacks, mas talvez este é um pouco mais elegante do que alguns dos outros, uma vez chamando sintaxe seria semelhante ao que você quer e você não teria necessidade de modificar as classes originais em tudo:

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;
};

Agora você pode fazer qualquer um destes para construir um objeto:

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

Com certeza, alguns podem não gostar modificando o protótipo do objeto Function, para que possa fazê-lo desta forma e usá-lo como uma função em vez disso:

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;
};

E então você iria chamá-lo assim:

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

Então, eu espero que aqueles são úteis para alguém - eles permitem que você deixe as funções de construtor originais sozinho e conseguir o que você está depois de uma simples linha de código (ao contrário das duas linhas que você precisa para a solução / solução alternativa atualmente selecionado .

O feedback é bem-vindo e apreciado.


UPDATE: Uma outra coisa a nota - tente criar instâncias do mesmo tipo com esses diferentes métodos e, em seguida, verificar para ver se as suas propriedades construtor são os mesmos - você pode querer que seja o caso, se você precisar de verificar a tipo de um objecto. O que quero dizer é melhor ilustrado pelo seguinte 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);

Tente isso com alguns dos outros hacks e ver o que acontece. Idealmente, você quer que eles sejam do mesmo tipo.


ressalva: Como observado nos comentários, isto não vai funcionar para aquelas funções construtoras que são criados usando a sintaxe função anônima, ou seja

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

A menos você criá-los assim:

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

A solução que eu forneci acima pode ou não ser útil para você, você precisa entender exatamente o que você está fazendo para chegar à melhor solução para suas necessidades específicas, e você precisa estar ciente do que está acontecendo a fazer minha solução "trabalho". Se você não entender como meus trabalhos solução, gastar tempo para descobrir isso.


solução alternativa: Não um a ignorar outras opções, aqui está uma das outras maneiras que você poderia esfolar este gato (com ressalvas semelhantes à abordagem acima), este um pouco mais esotérico:

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;
};

Aproveite!

que sobre uma solução alternativa?

function MyClass(arg1, arg2) {

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

    init(arg1, arg2);
}

Então, como você pode:

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

Uma possibilidade é fazer o trabalho construtor como uma chamada de função normal.

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

    // normal constructor here
}

A condição na declaração if será verdade se você chamar MyClass como uma função normal (inclusive com call / apply enquanto o argumento this não é um MyClass object).

Agora, todos estes são equivalentes:

new MyClass(arg1, arg2);
MyClass(arg1, arg2);
MyClass.call(null, arg1, arg2);
MyClass.apply(null, [arg1, arg2]);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top