484 Stimmen

Verwendung von .apply() mit dem Operator "new". Ist dies möglich?

In JavaScript möchte ich eine Objektinstanz erstellen (über die new Operator), aber übergeben Sie eine beliebige Anzahl von Argumenten an den Konstruktor. Ist dies möglich?

Was ich tun möchte, ist etwas wie dieses (aber der Code unten funktioniert nicht):

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

Die Antwort

Aus den Antworten hier wurde deutlich, dass es keine eingebaute Möglichkeit gibt, die .apply() mit dem new Betreiber. Die Leute haben jedoch eine Reihe wirklich interessanter Lösungen für dieses Problem vorgeschlagen.

Meine bevorzugte Lösung war dieser hier von Matthew Crumley (Ich habe es geändert, um die arguments Eigenschaft):

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

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

387voto

user123444555621 Punkte 139356

Mit ECMAScript5s Function.prototype.bind werden die Dinge ziemlich sauber:

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
}

Sie kann wie folgt verwendet werden:

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

oder sogar direkt:

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

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

Dies und die Eval-basierte Lösung sind die einzigen, die immer funktionieren, sogar mit speziellen Konstruktoren wie Date :

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

editar

Eine kleine Erklärung: Wir müssen die new bei einer Funktion, die eine begrenzte Anzahl von Argumenten benötigt. Die bind Methode ermöglicht es uns, dies wie folgt zu tun:

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

El anything Parameter spielt keine große Rolle, da der new Schlüsselwort-Rückstellungen f den Kontext. Er ist jedoch aus syntaktischen Gründen erforderlich. Nun, für die bind anrufen: Wir müssen eine variable Anzahl von Argumenten übergeben, so dass dies den Zweck erfüllt:

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

Lassen Sie uns das in eine Funktion einpacken. Cls wird als Argument 0 übergeben, also wird es unser anything .

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

In der Tat, die vorübergehende f ist überhaupt nicht erforderlich:

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

Schließlich sollten wir sicherstellen, dass bind ist das, was wir wirklich brauchen. ( Cls.bind überschrieben worden sein könnte). Ersetzen Sie sie also durch Function.prototype.bind und wir erhalten das endgültige Ergebnis wie oben.

260voto

Matthew Crumley Punkte 98564

Hier ist eine verallgemeinerte Lösung, die jeden Konstruktor aufrufen kann (außer nativen Konstruktoren, die sich anders verhalten, wenn sie als Funktionen aufgerufen werden, wie String , Number , Date , usw.) mit einer Reihe von Argumenten:

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

Ein Objekt, das durch den Aufruf von construct(Class, [1, 2, 3]) wäre identisch mit einem Objekt, das mit new Class(1, 2, 3) .

Sie könnten auch eine spezifischere Version erstellen, damit Sie nicht jedes Mal den Konstruktor übergeben müssen. Dies ist auch etwas effizienter, da nicht bei jedem Aufruf eine neue Instanz der inneren Funktion erstellt werden muss.

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

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

Der Grund für das Erstellen und Aufrufen der äußeren anonymen Funktion auf diese Weise ist, dass die Funktion F den globalen Namespace nicht zu verschmutzen. Dies wird manchmal als Modulmuster bezeichnet.

[UPDATE]

Für diejenigen, die dies in TypeScript verwenden möchten, da TS einen Fehler ausgibt, wenn F etwas zurückgibt:

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

37voto

thefourtheye Punkte 219812

Wenn Ihre Umgebung Folgendes unterstützt Der Spread-Operator von ECMA Script 2015 ( ... ) können Sie es einfach wie folgt verwenden

function Something() {
    // init stuff
}

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

Anmerkung: Jetzt, da die Spezifikationen der ECMA Script 2015 veröffentlicht sind und die meisten JavaScript-Engines sie aktiv implementieren, wäre dies der bevorzugte Weg, dies zu tun.

Sie können die Unterstützung des Spread-Betreibers in einigen der wichtigsten Umgebungen überprüfen, aquí .

27voto

substack Punkte 3346

Nehmen wir an, Sie haben einen Items-Konstruktor, der alle Argumente aufsaugt, die Sie ihm vorlegen:

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

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

Sie können eine Instanz mit Object.create() erstellen und dann .apply() mit dieser Instanz anwenden:

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

console.log(items.sum());

Das Ergebnis ist 10, denn 1 + 2 + 3 + 4 == 10:

$ node t.js
10

25voto

gfaceless Punkte 1439

In ES6, Reflect.construct() ist sehr praktisch:

Reflect.construct(F, args)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X