Come posso costruire un oggetto usando una matrice di valori per i parametri, piuttosto che elencarli, in JavaScript?

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

Domanda

È possibile? Sto creando una singola funzione factory di base per guidare fabbriche di diversi tipi (ma hanno alcune somiglianze) e voglio essere in grado di passare argomenti come array alla factory di base che quindi crea eventualmente un'istanza di un nuovo oggetto popolando gli argomenti di il costruttore della classe pertinente tramite un array.

In JavaScript è possibile utilizzare un array per chiamare una funzione con più argomenti utilizzando il metodo apply:

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

Non sembra che ci sia comunque modo di creare un'istanza di un oggetto usando Apply, vero?

Qualcosa del genere (non funziona):

var instance = new MyClass.apply(namespace, r);
È stato utile?

Soluzione

Prova questo:

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

Tutte le parole chiave " new " fa è passare un nuovo oggetto al costruttore che diventa quindi questa variabile all'interno della funzione di costruzione.

A seconda di come è stato scritto il costruttore, potresti doverlo fare:

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

Aggiornamento: un commento dice che non funziona se c'è un prototipo. Prova questo.

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

newApply( MyClass, args);

Altri suggerimenti

Nota che

  • new myClass()
    

    senza argomenti può fallire, poiché la funzione di costruzione può fare affidamento sull'esistenza di argomenti.

  • myClass.apply(something, args)
    

    fallirà in molti casi, specialmente se chiamato su classi native come Date o Number.

So che "eval è malvagio", ma in questo caso potresti voler provare quanto segue:

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

Semplice come quello.

(Funziona come Instance.New in questo post sul blog )

Gli hack sono hack sono hack, ma forse questo è un po 'più elegante di alcuni degli altri, poiché chiamare la sintassi sarebbe simile a quello che vuoi e non avresti bisogno di modificare affatto le classi originali:

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

Ora puoi fare una di queste due cose per costruire un oggetto:

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

Certo, ad alcuni potrebbe non piacere modificare il prototipo dell'oggetto Function, quindi puoi farlo in questo modo e usarlo come funzione invece:

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 poi lo chiameresti così:

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

Quindi, spero che siano utili a qualcuno: ti consentono di lasciare da sole le funzioni del costruttore originale e ottenere ciò che cerchi in una semplice riga di codice (a differenza delle due righe necessarie per la soluzione / soluzione attualmente selezionata .

Il feedback è benvenuto e apprezzato.


AGGIORNAMENTO: Un'altra cosa da notare: prova a creare istanze dello stesso tipo con questi diversi metodi e quindi a verificare se le loro proprietà del costruttore sono uguali, potresti volerlo fare se dovessi mai controllare tipo di un oggetto. Quello che intendo è meglio illustrato dal seguente codice:

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

Provalo con alcuni degli altri hack e vedi cosa succede. Idealmente, vuoi che siano dello stesso tipo.


CAVEAT: come notato nei commenti, questo non funzionerà per quelle funzioni di costruzione create utilizzando la sintassi delle funzioni anonime, ad esempio

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

A meno che non le crei in questo modo:

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

La soluzione che ho fornito sopra potrebbe essere o non essere utile per te, devi capire esattamente cosa stai facendo per arrivare alla soluzione migliore per le tue esigenze particolari e devi essere consapevole di ciò che sta per fare la mia soluzione "lavoro". Se non capisci come funziona la mia soluzione, dedica del tempo a scoprirla.


SOLUZIONE ALTERNATA: Non uno per trascurare altre opzioni, ecco uno degli altri modi in cui potresti scuoiare questo gatto (con avvertenze simili all'approccio sopra), questo un po 'più esoterico:

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

Enjoy!

che ne dici di una soluzione alternativa?

function MyClass(arg1, arg2) {

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

    init(arg1, arg2);
}

Quindi come puoi:

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

Una possibilità è far funzionare il costruttore come una normale chiamata di funzione.

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

    // normal constructor here
}

La condizione sull'istruzione if sarà vera se si chiama MyClass come una normale funzione (incluso con call / si applicano purché l'argomento this non sia un oggetto MyClass ).

Ora tutti questi sono equivalenti:

new MyClass(arg1, arg2);
MyClass(arg1, arg2);
MyClass.call(null, arg1, arg2);
MyClass.apply(null, [arg1, arg2]);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top