L'utilisation d'.appliquer() avec les "nouveaux" de l'opérateur. Est-ce possible?

en JavaScript, je veux créer une instance objet (via l'opérateur new ), mais passer un nombre arbitraire d'arguments au constructeur. Est-ce possible?

ce que je veux faire est quelque chose comme ceci (mais le code ci-dessous ne fonctionne pas):

function Something(){
    // init stuff
}
function createSomething(){
    return new Something.apply(null, arguments);
}
var s = createSomething(a,b,c); // 's' is an instance of Something

La Réponse

D'après les réponses ici, il est devenu clair qu'il n'y a pas de manière intégrée d'appeler .apply() avec l'opérateur new . Cependant, les gens ont suggéré un certain nombre de solutions vraiment intéressantes au problème.

ma solution préférée était celle-ci de Matthew Crumley (Je l'ai modifié pour passer la propriété arguments ):

var createSomething = (function() {
    function F(args) {
        return Something.apply(this, args);
    }
    F.prototype = Something.prototype;

    return function() {
        return new F(arguments);
    }
})();
429
demandé sur Prisoner ZERO 2009-10-22 16:15:09
la source

30 ответов

Avec ECMAScript5 Function.prototype.bind les choses deviennent assez propre:

function newCall(Cls) {
    return new (Function.prototype.bind.apply(Cls, arguments));
    // or even
    // return new (Cls.bind.apply(Cls, arguments));
    // if you know that Cls.bind has not been overwritten
}

Il peut être utilisé comme suit:

var s = newCall(Something, a, b, c);

ou même directement:

var s = new (Function.prototype.bind.call(Something, null, a, b, c));

var s = new (Function.prototype.bind.apply(Something, [null, a, b, c]));

et le eval solution basée sur le sont les seuls qui fonctionnent toujours, même avec des constructeurs spéciaux comme Date :

var date = newCall(Date, 2012, 1);
console.log(date instanceof Date); // true

modifier

Un peu d'explication: Nous devons exécuter new sur une fonction qui prend un nombre limité d'arguments. La méthode bind nous permet de le faire comme ceci:

var f = Cls.bind(anything, arg1, arg2, ...);
result = new f();

le paramètre anything n'a pas beaucoup d'importance, puisque le mot-clé new réinitialise le contexte f . Toutefois, elle est requise pour des raisons syntaxiques. Maintenant, pour l'appel bind : nous devons passer une variable nombre d'arguments, donc cela fait l'affaire:

var f = Cls.bind.apply(Cls, [anything, arg1, arg2, ...]);
result = new f();

enveloppons ça dans une fonction. Cls est passé en tant qu'arugment 0, donc ça va être notre anything .

function newCall(Cls /*, arg1, arg2, ... */) {
    var f = Cls.bind.apply(Cls, arguments);
    return new f();
}

en fait, la variable temporaire f n'est pas nécessaire du tout:

function newCall(Cls /*, arg1, arg2, ... */) {
    return new (Cls.bind.apply(Cls, arguments))();
}

Enfin, nous devons nous assurer que bind est vraiment ce dont nous avons besoin. ( Cls.bind peut avoir été remplacé). Pour le remplacer par Function.prototype.bind , et nous obtenons le résultat final comme ci-dessus.

330
répondu user123444555621 2017-05-23 15:26:38
la source

Voici une solution généralisée qui peut appeler n'importe quel constructeur (sauf les constructeurs natifs qui se comportent différemment lorsqu'ils sont appelés en tant que fonctions, comme String , Number , Date , etc.) avec un tableau d'arguments:

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

un objet créé en appelant construct(Class, [1, 2, 3]) serait identique à un objet créé avec new Class(1, 2, 3) .

vous pouvez aussi faire une version plus spécifique pour ne pas avoir à passer le constructeur chaque fois. C'est aussi un peu plus efficace, puisqu'il n'a pas besoin de créer une nouvelle instance de la fonction interne à chaque fois que vous l'appelez.

var createSomething = (function() {
    function F(args) {
        return Something.apply(this, args);
    }
    F.prototype = Something.prototype;

    return function(args) {
        return new F(args);
    }
})();

la raison pour créer et appeler la fonction extérieure anonyme comme celle-ci est de garder la fonction F de polluer l'espace-Nom global. On l'appelle parfois le modèle du module.

[mise à JOUR]

pour ceux qui veulent utiliser ceci en écriture dactylographiée, puisque TS donne une erreur si F renvoie n'importe quoi:

function construct(constructor, args) {
    function F() : void {
        constructor.apply(this, args);
    }
    F.prototype = constructor.prototype;
    return new F();
}
254
répondu Matthew Crumley 2014-07-28 21:24:50
la source

si votre environnement supporte l'opérateur spread de ECMA Script 2015 ( ... ) , vous pouvez simplement l'utiliser comme ceci

function Something() {
    // init stuff
}

function createSomething() {
    return new Something(...arguments);
}

Note: maintenant que les spécifications du Script ECMA 2015 sont publiées et que la plupart des moteurs JavaScript l'implémentent activement, ce serait la meilleure façon de procéder.

vous pouvez vérifier le soutien de L'opérateur de propagation dans quelques-uns des environnements majeurs, ici .

28
répondu thefourtheye 2015-09-13 12:38:51
la source

supposons que vous ayez un constructeur D'articles qui dégrade tous les arguments que vous lui lancez:

function Items () {
    this.elems = [].slice.call(arguments);
}

Items.prototype.sum = function () {
    return this.elems.reduce(function (sum, x) { return sum + x }, 0);
};

vous pouvez créer une instance avec Object.create() et puis .appliquer() avec cette instance:

var items = Object.create(Items.prototype);
Items.apply(items, [ 1, 2, 3, 4 ]);

console.log(items.sum());

qui, quand lancer imprime 10 depuis 1 + 2 + 3 + 4 == 10:

$ node t.js
10
26
répondu substack 2011-05-19 21:18:47
la source

en ES6, Reflect.construct() est tout à fait pratique:

Reflect.construct(F, args)
12
répondu gfaceless 2016-07-21 12:00:54
la source

@Matthew Je pense qu'il vaut mieux aussi réparer la propriété du constructeur.

// Invoke new operator with arbitrary arguments
// Holy Grail pattern
function invoke(constructor, args) {
    var f;
    function F() {
        // constructor returns **this**
        return constructor.apply(this, args);
    }
    F.prototype = constructor.prototype;
    f = new F();
    f.constructor = constructor;
    return f;
}
9
répondu wukong 2012-12-18 15:56:53
la source

une version améliorée de la réponse de @Matthew. Cette forme a les petits avantages de performance obtenus en stockant la classe temp dans une fermeture, ainsi que la flexibilité d'avoir une fonction pouvant être utilisé pour créer n'importe quelle classe

var applyCtor = function(){
    var tempCtor = function() {};
    return function(ctor, args){
        tempCtor.prototype = ctor.prototype;
        var instance = new tempCtor();
        ctor.prototype.constructor.apply(instance,args);
        return instance;
    }
}();

ce nom serait utilisé en appelant applyCtor(class, [arg1, arg2, argn]);

8
répondu jordancpaul 2016-02-18 00:45:37
la source

vous pourriez déplacer la substance init dans une méthode séparée de Something de l 'prototype:

function Something() {
    // Do nothing
}

Something.prototype.init = function() {
    // Do init stuff
};

function createSomething() {
    var s = new Something();
    s.init.apply(s, arguments);
    return s;
}

var s = createSomething(a,b,c); // 's' is an instance of Something
7
répondu Tim Down 2011-11-18 03:25:00
la source

cette réponse est un peu tardive, mais j'ai pensé que quiconque la verrait pourrait l'utiliser. Il y a un moyen de retourner un nouvel objet en utilisant apply. Bien qu'il nécessite un petit changement à votre déclaration d'objet.

function testNew() {
    if (!( this instanceof arguments.callee ))
        return arguments.callee.apply( new arguments.callee(), arguments );
    this.arg = Array.prototype.slice.call( arguments );
    return this;
}

testNew.prototype.addThem = function() {
    var newVal = 0,
        i = 0;
    for ( ; i < this.arg.length; i++ ) {
        newVal += this.arg[i];
    }
    return newVal;
}

testNew( 4, 8 ) === { arg : [ 4, 8 ] };
testNew( 1, 2, 3, 4, 5 ).addThem() === 15;

pour le premier if énoncé de travail dans testNew vous devez return this; au bas de la fonction. Comme exemple avec votre code:

function Something() {
    // init stuff
    return this;
}
function createSomething() {
    return Something.apply( new Something(), arguments );
}
var s = createSomething( a, b, c );

mise à Jour: j'ai changé mon premier exemple pour faire la somme d'un nombre quelconque d'arguments, au lieu de deux.

6
répondu Trevor Norris 2011-09-14 22:36:59
la source

je viens de rencontrer ce problème, et je l'ai résolu comme ceci:

function instantiate(ctor) {
    switch (arguments.length) {
        case 1: return new ctor();
        case 2: return new ctor(arguments[1]);
        case 3: return new ctor(arguments[1], arguments[2]);
        case 4: return new ctor(arguments[1], arguments[2], arguments[3]);
        //...
        default: throw new Error('instantiate: too many parameters');
    }
}

function Thing(a, b, c) {
    console.log(a);
    console.log(b);
    console.log(c);
}

var thing = instantiate(Thing, 'abc', 123, {x:5});

Ouais, c'est un peu moche, mais il résout le problème, et c'est très simple.

6
répondu alekop 2014-07-26 00:09:21
la source

si vous êtes intéressé par une solution basée sur eval

function createSomething() {
    var q = [];
    for(var i = 0; i < arguments.length; i++)
        q.push("arguments[" + i + "]");
    return eval("new Something(" + q.join(",") + ")");
}
3
répondu user187291 2009-10-22 17:25:53
la source

Voir aussi comment CoffeeScript le fait.

s = new Something([a,b,c]...)

devient:

var s;
s = (function(func, args, ctor) {
  ctor.prototype = func.prototype;
  var child = new ctor, result = func.apply(child, args);
  return Object(result) === result ? result : child;
})(Something, [a, b, c], function(){});
3
répondu mbarkhau 2016-09-01 10:41:59
la source

ça marche!

var cls = Array; //eval('Array'); dynamically
var data = [2];
new cls(...data);
3
répondu infinito84 2017-02-03 18:33:06
la source

cette approche de constructeur fonctionne à la fois avec et sans le new mot clé:

function Something(foo, bar){
  if (!(this instanceof Something)){
    var obj = Object.create(Something.prototype);
    return Something.apply(obj, arguments);
  }
  this.foo = foo;
  this.bar = bar;
  return this;
}

il suppose le soutien pour Object.create , mais vous pouvez toujours polyfill que si vous prenez en charge les navigateurs plus anciens. voir la table de support sur MDN ici .

voici un JSBin pour le voir en action avec la sortie de la console .

2
répondu muffinresearch 2015-03-13 19:51:32
la source

vous ne pouvez pas appeler un constructeur avec un nombre variable d'arguments comme vous voulez avec l'opérateur new .

ce que vous pouvez faire est de changer légèrement le constructeur. Au lieu de:

function Something() {
    // deal with the "arguments" array
}
var obj = new Something.apply(null, [0, 0]);  // doesn't work!

faites ceci à la place:

function Something(args) {
    // shorter, but will substitute a default if args.x is 0, false, "" etc.
    this.x = args.x || SOME_DEFAULT_VALUE;

    // longer, but will only put in a default if args.x is not supplied
    this.x = (args.x !== undefined) ? args.x : SOME_DEFAULT_VALUE;
}
var obj = new Something({x: 0, y: 0});

Ou si vous devez utiliser un tableau:

function Something(args) {
    var x = args[0];
    var y = args[1];
}
var obj = new Something([0, 0]);
1
répondu Anthony Mills 2009-10-23 01:31:23
la source

Matthieu Crumley solutions en CoffeeScript:

construct = (constructor, args) ->
    F = -> constructor.apply this, args
    F.prototype = constructor.prototype
    new F

ou

createSomething = (->
    F = (args) -> Something.apply this, args
    F.prototype = Something.prototype
    return -> new Something arguments
)()
1
répondu Benjie 2017-05-23 15:26:38
la source
function createSomething() {
    var args = Array.prototype.concat.apply([null], arguments);
    return new (Function.prototype.bind.apply(Something, args));
}

si votre navigateur cible ne supporte pas ECMAScript 5 Function.prototype.bind , le code ne fonctionnera pas. Il n'est pas très probable cependant, voir tableau de compatibilité .

1
répondu user2683246 2013-08-16 05:23:55
la source

modifié @Matthieu réponse. Ici je peux passer n'importe quel nombre de paramètres pour fonctionner comme d'habitude (pas de tableau).

function createObject( constr ) {   
  var args =  arguments;
  var wrapper =  function() {  
    return constr.apply( this, Array.prototype.slice.call(args, 1) );
  }

  wrapper.prototype =  constr.prototype;
  return  new wrapper();
}


function Something() {
    // init stuff
};

var obj1 =     createObject( Something, 1, 2, 3 );
var same =     new Something( 1, 2, 3 );
1
répondu Eugen Konkov 2015-04-04 17:22:47
la source

Ce one-liner devrait le faire:

new (Function.prototype.bind.apply(Something, [null].concat(arguments)));
1
répondu aleemb 2015-07-10 15:43:08
la source

Solution sans ES6 ou polyfills:

var obj = _new(Demo).apply(["X", "Y", "Z"]);


function _new(constr)
{
    function createNamedFunction(name)
    {
        return (new Function("return function " + name + "() { };"))();
    }

    var func = createNamedFunction(constr.name);
    func.prototype = constr.prototype;
    var self = new func();

    return { apply: function(args) {
        constr.apply(self, args);
        return self;
    } };
}

function Demo()
{
    for(var index in arguments)
    {
        this['arg' + (parseInt(index) + 1)] = arguments[index];
    }
}
Demo.prototype.tagged = true;


console.log(obj);
console.log(obj.tagged);



résultats



Démo {arg1: "X", arg2: "Y", arg3: "Z"}





... ou" plus court "chemin:

var func = new Function("return function " + Demo.name + "() { };")();
func.prototype = Demo.prototype;
var obj = new func();

Demo.apply(obj, ["X", "Y", "Z"]);



modifier:

Je pense que cela pourrait être une bonne solution:

this.forConstructor = function(constr)
{
    return { apply: function(args)
    {
        let name = constr.name.replace('-', '_');

        let func = (new Function('args', name + '_', " return function " + name + "() { " + name + "_.apply(this, args); }"))(args, constr);
        func.constructor = constr;
        func.prototype = constr.prototype;

        return new func(args);
    }};
}
1
répondu Martin Wantke 2017-09-04 13:19:30
la source

il est également intéressant de voir comment la question de la réutilisation du constructeur temporaire F() , a été abordée en utilisant arguments.callee , alias la fonction créateur / usine elle-même: http://www.dhtmlkitchen.com/?category=/JavaScript/&date=2008/05/11/&entry=Decorator-Factory-Aspect

0
répondu polaretto 2011-05-20 20:08:42
la source

Toute fonction (même un constructeur) peut prendre un nombre variable d'arguments. Chaque fonction a une variable" arguments "qui peut être coulée dans un tableau avec [].slice.call(arguments) .

function Something(){
  this.options  = [].slice.call(arguments);

  this.toString = function (){
    return this.options.toString();
  };
}

var s = new Something(1, 2, 3, 4);
console.log( 's.options === "1,2,3,4":', (s.options == '1,2,3,4') );

var z = new Something(9, 10, 11);
console.log( 'z.options === "9,10,11":', (z.options == '9,10,11') );

les essais ci-dessus donnent les résultats suivants:

s.options === "1,2,3,4": true
z.options === "9,10,11": true
0
répondu Wil Moore III 2012-09-18 21:10:46
la source
function FooFactory() {
    var prototype, F = function(){};

    function Foo() {
        var args = Array.prototype.slice.call(arguments),
            i;     
        for (i = 0, this.args = {}; i < args.length; i +=1) {
            this.args[i] = args[i];
        }
        this.bar = 'baz';
        this.print();

        return this;
    }

    prototype = Foo.prototype;
    prototype.print = function () {
        console.log(this.bar);
    };

    F.prototype = prototype;

    return Foo.apply(new F(), Array.prototype.slice.call(arguments));
}

var foo = FooFactory('a', 'b', 'c', 'd', {}, function (){});
console.log('foo:',foo);
foo.print();
0
répondu user2217522 2013-12-17 05:45:14
la source

voici ma version de createSomething :

function createSomething() {
    var obj = {};
    obj = Something.apply(obj, arguments) || obj;
    obj.__proto__ = Something.prototype; //Object.setPrototypeOf(obj, Something.prototype); 
    return o;
}

basé sur cela, j'ai essayé de simuler le mot-clé new de JavaScript:

//JavaScript 'new' keyword simulation
function new2() {
    var obj = {}, args = Array.prototype.slice.call(arguments), fn = args.shift();
    obj = fn.apply(obj, args) || obj;
    Object.setPrototypeOf(obj, fn.prototype); //or: obj.__proto__ = fn.prototype;
    return obj;
}

je l'ai testé et il semble que cela fonctionne parfaitement bien pour tous les scénarios. Il fonctionne également sur les constructeurs natifs comme Date . Voici quelques tests:

//test
new2(Something);
new2(Something, 1, 2);

new2(Date);         //"Tue May 13 2014 01:01:09 GMT-0700" == new Date()
new2(Array);        //[]                                  == new Array()
new2(Array, 3);     //[undefined × 3]                     == new Array(3)
new2(Object);       //Object {}                           == new Object()
new2(Object, 2);    //Number {}                           == new Object(2)
new2(Object, "s");  //String {0: "s", length: 1}          == new Object("s")
new2(Object, true); //Boolean {}                          == new Object(true)
0
répondu advncd 2014-06-02 11:11:18
la source

bien que les autres approches soient réalisables, elles sont indûment complexes. Dans Clojure, vous créez généralement une fonction qui instancie les types/enregistrements et utilisez cette fonction comme mécanisme d'instanciation. Traduire ceci en JavaScript:

function Person(surname, name){
  this.surname = surname;
  this.name = name;
}

function person(surname, name){ 
  return new Person(surname, name);
}

en adoptant cette approche vous évitez l'utilisation de new sauf comme décrit ci-dessus. Et cette fonction, bien sûr, n'a aucun problème à travailler avec apply ou tout autre nombre de programmation fonctionnelle caractéristique.

var doe  = _.partial(person, "Doe");
var john = doe("John");
var jane = doe("Jane");

en utilisant cette approche, tous vos constructeurs de type (par exemple Person ) sont des constructeurs vanille, Ne-rien. Vous passez juste dans les arguments et les assignez aux propriétés du même nom. Les détails Velus vont dans la fonction constructeur (par exemple person ).

il est de peu de peine avoir à créer ces fonctions de constructeur supplémentaires car ils sont une bonne pratique de toute façon. Ils peuvent être pratique puisqu'ils vous permettent avoir potentiellement plusieurs fonctions de constructeur avec des nuances différentes.

0
répondu Mario 2016-10-14 19:27:22
la source

Oui nous pouvons, javascript est plus de prototype inheritance dans la nature.

function Actor(name, age){
  this.name = name;
  this.age = age;
}

Actor.prototype.name = "unknown";
Actor.prototype.age = "unknown";

Actor.prototype.getName = function() {
    return this.name;
};

Actor.prototype.getAge = function() {
    return this.age;
};

quand nous créons un objet avec" new " alors notre objet créé hérite getAge (), mais si nous avons utilisé apply(...) or call(...) pour appeler acteur, alors nous passons un objet pour "this" mais l'objet que nous passons WON'T hériter de Actor.prototype

sauf si, nous passons directement appliquer ou appeler L'acteur.prototype mais alors.... "ceci" indiquerait "Acteur.prototype" et ceci.nom serait écrit à: Actor.prototype.name . Affectant ainsi tous les autres objets créés avec Actor... puisque nous écrasons le prototype plutôt que l'instance

var rajini = new Actor('Rajinikanth', 31);
console.log(rajini);
console.log(rajini.getName());
console.log(rajini.getAge());

var kamal = new Actor('kamal', 18);
console.log(kamal);
console.log(kamal.getName());
console.log(kamal.getAge());

essayons avec apply

var vijay = Actor.apply(null, ["pandaram", 33]);
if (vijay === undefined) {
    console.log("Actor(....) didn't return anything 
           since we didn't call it with new");
}

var ajith = {};
Actor.apply(ajith, ['ajith', 25]);
console.log(ajith); //Object {name: "ajith", age: 25}
try {
    ajith.getName();
} catch (E) {
    console.log("Error since we didn't inherit ajith.prototype");
}
console.log(Actor.prototype.age); //Unknown
console.log(Actor.prototype.name); //Unknown

en passant Actor.prototype à Actor.call() comme premier argument, lorsque la fonction Actor() est exécutée, elle exécute this.name=name , puisque "ce" pointera vers Actor.prototype , this.name=name; means Actor.prototype.name=name;

var simbhu = Actor.apply(Actor.prototype, ['simbhu', 28]);
if (simbhu === undefined) {
    console.log("Still undefined since the function didn't return anything.");
}
console.log(Actor.prototype.age); //simbhu
console.log(Actor.prototype.name); //28

var copy = Actor.prototype;
var dhanush = Actor.apply(copy, ["dhanush", 11]);
console.log(dhanush);
console.log("But now we've corrupted Parent.prototype in order to inherit");
console.log(Actor.prototype.age); //11
console.log(Actor.prototype.name); //dhanush

pour revenir à la question initiale comment utiliser new operator with apply , voici mon avis....

Function.prototype.new = function(){
    var constructor = this;
    function fn() {return constructor.apply(this, args)}
    var args = Array.prototype.slice.call(arguments);
    fn.prototype = this.prototype;
    return new fn
};

var thalaivar = Actor.new.apply(Parent, ["Thalaivar", 30]);
console.log(thalaivar);
0
répondu Thalaivar 2016-10-14 23:11:44
la source

puisque ES6 cela est possible à travers l'opérateur de propagation, voir https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Apply_for_new

cette réponse était déjà, en quelque sorte donnée dans le commentaire https://stackoverflow.com/a/42027742/7049810 , mais semble avoir été manquée par la plupart

0
répondu Paul 2017-05-23 15:18:25
la source

en fait la méthode la plus simple est:

function Something (a, b) {
  this.a = a;
  this.b = b;
}
function createSomething(){
    return Something;
}
s = new (createSomething())(1, 2); 
// s == Something {a: 1, b: 2}
0
répondu user3184743 2018-02-27 10:53:32
la source

une solution révisée de la réponse de @jordancpaul.

var applyCtor = function(ctor, args)
{
    var instance = new ctor();
    ctor.prototype.constructor.apply(instance, args);
    return instance;
}; 
0
répondu tech-e 2018-03-05 23:14:20
la source

Merci aux messages ici je l'ai utilisé de cette façon:

SomeClass = function(arg1, arg2) {
    // ...
}

ReflectUtil.newInstance('SomeClass', 5, 7);

et mise en œuvre:

/**
 * @param strClass:
 *          class name
 * @param optionals:
 *          constructor arguments
 */
ReflectUtil.newInstance = function(strClass) {
    var args = Array.prototype.slice.call(arguments, 1);
    var clsClass = eval(strClass);
    function F() {
        return clsClass.apply(this, args);
    }
    F.prototype = clsClass.prototype;
    return new F();
};
-1
répondu Mykhaylo Adamovych 2011-08-05 00:41:58
la source

Autres questions sur javascript class oop constructor inheritance