Comment puis-je construire un objet en utilisant un tableau de valeurs pour les paramètres, plutôt que de les lister, en JavaScript?

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

Question

Est-ce possible? Je crée une fonction de fabrique de base unique pour gérer des fabriques de types différents (mais présente certaines similitudes) et je veux pouvoir transmettre des arguments sous forme de tableau à la fabrique de base, ce qui crée ensuite éventuellement une instance d'un nouvel objet remplissant les arguments de le constructeur de la classe concernée via un tableau.

En JavaScript, il est possible d'utiliser un tableau pour appeler une fonction à plusieurs arguments en utilisant la méthode 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);

Il ne semble pas qu'il soit possible de créer une instance d'objet avec apply, n'est-ce pas?

Quelque chose comme (ça ne marche pas):

var instance = new MyClass.apply(namespace, r);
Était-ce utile?

La solution

Essayez ceci:

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

Tous les mots clés "nouveau". ne transfère au constructeur un nouvel objet qui devient alors la variable this de la fonction constructeur.

En fonction de la manière dont le constructeur a été écrit, vous devrez peut-être procéder ainsi:

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

Mise à jour: un commentaire indique que cela ne fonctionne pas s'il existe un prototype. Essayez ceci.

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

newApply( MyClass, args);

Autres conseils

Notez que

  • new myClass()
    

    sans aucun argument peut échouer car la fonction constructeur peut s’appuyer sur l’existence d’arguments.

  • myClass.apply(something, args)
    

    échouera dans de nombreux cas, en particulier s'il est appelé avec des classes natives telles que Date ou Number.

Je sais que "eval is evil", mais dans ce cas, vous pouvez essayer les solutions suivantes:

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

Aussi simple que cela.

(Cela fonctionne de la même manière que Instance.New dans cet article de blog )

Les hacks sont des hacks, mais celui-ci est peut-être un peu plus élégant que d’autres, car la syntaxe d’appel serait semblable à ce que vous voulez et vous n’auriez pas besoin de modifier les classes d’origine:

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

Vous pouvez maintenant créer l’un des objets suivants:

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

Certes, certains n'aimeront peut-être pas modifier le prototype de l'objet Function. Vous pouvez donc le faire de cette façon et l'utiliser comme une fonction:

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

Et ensuite, vous l'appeleriez comme suit:

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

J'espère donc que ceux-ci seront utiles à quelqu'un - ils vous permettront de laisser les fonctions de constructeur d'origine et d'obtenir ce que vous voulez dans une seule ligne de code (contrairement aux deux lignes dont vous avez besoin pour la solution / solution de contournement sélectionnée .

Les commentaires sont les bienvenus et appréciés.

UPDATE: Une autre chose à noter - essayez de créer des occurrences du même type avec ces différentes méthodes, puis vérifiez si leurs propriétés de constructeur sont identiques. Vous souhaiterez peut-être que ce soit le cas si vous avez besoin de vérifier la type d'objet. Ce que je veux dire est mieux illustré par le code suivant:

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

Essayez cela avec certains des autres hacks et voyez ce qui se passe. Idéalement, vous voulez qu'ils soient du même type.

CAVEAT: Comme indiqué dans les commentaires, cela ne fonctionnera pas pour les fonctions de constructeur créées à l'aide de la syntaxe de fonction anonyme, c'est-à-dire

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

Sauf si vous les créez comme suit:

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

La solution que j'ai fournie ci-dessus peut vous être utile ou non. Vous devez comprendre exactement ce que vous faites pour trouver la meilleure solution pour vos besoins particuliers. Vous devez également savoir ce qui se passe pour que ma solution " travail. " Si vous ne comprenez pas le fonctionnement de ma solution, prenez le temps de la comprendre.

SOLUTION ALTERNATIVE: Il ne faut pas négliger les autres options, voici l'une des autres façons de traiter ce chat (avec des mises en garde similaires à l'approche ci-dessus), celle-ci un peu plus ésotérique:

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

Profitez!

Qu'en est-il d'une solution de contournement?

function MyClass(arg1, arg2) {

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

    init(arg1, arg2);
}

Alors, comment pouvez-vous:

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

Une possibilité est de faire fonctionner le constructeur comme un appel de fonction normal.

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

    // normal constructor here
}

La condition sur l'instruction si est vraie si vous appelez MyClass en tant que fonction normale (y compris avec appel / ) tant que l'argument this n'est pas un objet MyClass ).

Tous ces éléments sont équivalents:

new MyClass(arg1, arg2);
MyClass(arg1, arg2);
MyClass.call(null, arg1, arg2);
MyClass.apply(null, [arg1, arg2]);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top