483 votes

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

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

Ce que je veux faire, c'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

À partir des réponses ici, il est devenu clair qu'il n'y a pas intégré de manière à appeler .apply() avec l' new de l'opérateur. Cependant, les gens ont suggéré un certain nombre de vraiment intéressant de solutions à ce problème.

Ma solution préférée était celle de Matthieu Crumley (je l'ai modifié pour passer l' arguments de la propriété):

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

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

387voto

Pumbaa80 Points 27066

Avec ECMAScipt5 de l' 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]));

Le présent et l' eval-fondé de la solution 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 avons besoin pour exécuter new sur une fonction qui prend un nombre limité d'arguments. L' bind méthode nous permet de le faire comme ceci:

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

L' anything paramètre n'a pas beaucoup d'importance, puisque l' new mot-clé réinitialise fs'contexte. Cependant, il est nécessaire pour des raisons de syntaxe. Maintenant, pour l' bind appel: Nous avons besoin de passer un nombre variable d'arguments, si ce n'est l'astuce:

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

Laisser envelopper que dans une fonction. Cls est passé comme arugment 0, donc ça va être notre anything.

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

Acutally, temporaire f variable n'est pas nécessaire à tous:

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

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

259voto

Matthew Crumley Points 47284

Voici une solution généralisée que l'on peut appeler de tout constructeur (sauf natif de constructeurs qui se comportent différemment lorsqu'il est appelé 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éé par l'appel de construct(Class, [1, 2, 3]) serait identique à un objet créé avec new Class(1, 2, 3).

Vous pouvez aussi faire un plus de la version spécifique de sorte que vous n'avez pas à passer le constructeur à chaque fois. C'est également légèrement plus efficace, car il n'est pas nécessaire de créer une nouvelle instance de l'intérieur de la fonction à 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 de la création et de l'appel que l'extérieur de la fonction anonyme comme c'est de garder la fonction F de polluer l'espace de noms global. Il est parfois appelé le motif de module.

[Mise à JOUR]

Pour ceux qui veulent l'utiliser dans la Machine, depuis TS donne une erreur si F retourne rien:

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

27voto

substack Points 1496

Supposons que vous avez un constructeur qui slurps 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 d'Objet.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 lorsque vous exécutez imprime 10 depuis 1 + 2 + 3 + 4 == 10:

$ node t.js
10

9voto

wukong Points 849

@Matthieu Je pense qu'il est préférable de fixer le constructeur de propriété.

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

8voto

Tim Down Points 124501

Vous pouvez déplacer l'init des trucs dans une méthode distincte de l' Somethings'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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X