Como posso construir um objeto usando uma matriz de valores para os parâmetros, ao invés de listá-los para fora, em JavaScript?
-
03-07-2019 - |
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);
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]);