it-swarm.com.de

Verwendung von .apply () mit dem Operator 'new'. Ist das möglich?

In JavaScript möchte ich eine Objektinstanz erstellen (über den Operator new), dem Konstruktor jedoch eine beliebige Anzahl von Argumenten übergeben. Ist das möglich?

Was ich tun möchte, ist so etwas (aber der folgende Code 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 integrierte Möglichkeit gibt, .apply() mit dem Operator new aufzurufen. Die Leute schlugen jedoch eine Reihe wirklich interessanter Lösungen für das Problem vor.

Meine bevorzugte Lösung war diese von Matthew Crumley (Ich habe sie geändert, um die Eigenschaft arguments zu übergeben):

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

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

Mit ECMAScript5s Function.prototype.bind Die Dinge werden 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
}

Es kann wie folgt verwendet werden:

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

oder auch 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 auch mit speziellen Konstruktoren wie Date immer funktionieren:

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

edit

Ein bisschen Erklärung: Wir müssen new für eine Funktion ausführen, die eine begrenzte Anzahl von Argumenten akzeptiert. Mit der bind Methode können wir das so machen:

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

Der Parameter anything spielt keine große Rolle, da das Schlüsselwort new den Kontext von f zurücksetzt. Dies ist jedoch aus syntaktischen Gründen erforderlich. Nun zum bind -Aufruf: Wir müssen eine variable Anzahl von Argumenten übergeben, damit dies den Trick macht:

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

Lassen Sie uns das in eine Funktion packen. Cls wird als Arugment 0 übergeben, also wird es unser anything sein.

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

Tatsächlich wird die temporäre Variable f überhaupt nicht benötigt:

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

Schließlich sollten wir sicherstellen, dass bind wirklich das ist, was wir brauchen. (Cls.bind wurde möglicherweise überschrieben). Also ersetze es durch Function.prototype.bind, und wir erhalten das Endergebnis wie oben.

351

Hier ist eine verallgemeinerte Lösung, die jeden Konstruktor (mit Ausnahme von nativen Konstruktoren, die sich anders verhalten, wenn sie als Funktionen aufgerufen werden, z. B. String, Number, Date usw.) mit einem Array von aufrufen kann Argumente:

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

Ein Objekt, das durch Aufrufen von construct(Class, [1, 2, 3]) erstellt wurde, ist identisch mit einem Objekt, das mit new Class(1, 2, 3) erstellt wurde.

Sie können auch eine spezifischere Version erstellen, damit Sie den Konstruktor nicht jedes Mal ü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 ist, dass die Funktion F den globalen Namespace nicht verschmutzt. Es wird manchmal als Modulmuster bezeichnet.

[UPDATE]

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

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

Wenn Ihre Umgebung den Spread-Operator von ECMA Script 2015 (...) , du kannst es einfach so benutzen

function Something() {
    // init stuff
}

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

Hinweis: Nachdem die Spezifikationen für ECMA Script 2015 veröffentlicht wurden und die meisten JavaScript-Engines sie aktiv implementieren, ist dies die bevorzugte Methode.

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

30
thefourtheye

Angenommen, Sie haben einen Items-Konstruktor, der alle Argumente, die Sie darauf werfen, zusammenführt:

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 () und dann mit dieser Instanz .apply () erstellen:

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

console.log(items.sum());

Was beim Ausführen 10 seit 1 + 2 + 3 + 4 == 10 ausgibt:

$ node t.js
10
27
substack

In ES6 ist Reflect.construct() ziemlich praktisch:

Reflect.construct(F, args)
15
gfaceless

@ Matthew Ich denke, es ist besser, die Konstruktoreigenschaft auch zu beheben.

// 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
wukong

Eine verbesserte Version von @ Matthews Antwort. Diese Form hat die geringen Leistungsvorteile, die durch das Speichern der temporären Klasse in einem Abschluss erzielt werden, sowie die Flexibilität, dass eine Funktion zum Erstellen einer beliebigen Klasse verwendet werden kann

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

Dies würde durch Aufrufen von applyCtor(class, [arg1, arg2, argn]); verwendet

8
jordancpaul

Sie können das Init-Zeug in eine separate Methode des Prototyps von Something verschieben:

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
Tim Down

Diese Antwort ist etwas spät, aber es ist anzunehmen, dass jeder, der sie sieht, sie nutzen kann. Es gibt eine Möglichkeit, ein neues Objekt mit apply zurückzugeben. Es ist jedoch eine kleine Änderung Ihrer Objektdeklaration erforderlich.

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;

Damit die erste if Anweisung in testNew funktioniert, müssen Sie return this; am unteren Rand der Funktion. Also als Beispiel mit Ihrem Code:

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

pdate: Ich habe mein erstes Beispiel geändert, um eine beliebige Anzahl von Argumenten anstelle von nur zwei zu addieren.

6
Trevor Norris

Ich bin gerade auf dieses Problem gestoßen und habe es folgendermaßen gelöst:

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

Ja, es ist ein bisschen hässlich, aber es löst das Problem und es ist denkbar einfach.

6
alekop

wenn Sie an einer auf Evaluierung basierenden Lösung interessiert sind

function createSomething() {
    var q = [];
    for(var i = 0; i < arguments.length; i++)
        q.Push("arguments[" + i + "]");
    return eval("new Something(" + q.join(",") + ")");
}
4
user187291

Das funktioniert!

var cls = Array; //eval('Array'); dynamically
var data = [2];
new cls(...data);
4
infinito84

Siehe auch, wie CoffeeScript das macht.

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

wird:

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
mbarkhau

Dieser Konstruktoransatz funktioniert sowohl mit als auch ohne das Schlüsselwort new:

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

Es wird davon ausgegangen, dass Object.create aber Sie könnten das immer polyfüllen, wenn Sie ältere Browser unterstützen. Siehe die Support-Tabelle auf MDN hier .

Hier ist ein JSBin, um es in Aktion mit Konsolenausgabe zu sehen .

2
muffinresearch

Lösung ohne ES6 oder 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);


Ausgabe

Demo {arg1: "X", arg2: "Y", arg3: "Z"}


... oder "kürzer":

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

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


edit:
Ich denke, das könnte eine gute Lösung sein:

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);
    }};
}
2
Martin Wantke

Dieser Einzeiler sollte es tun:

new (Function.prototype.bind.apply(Something, [null].concat(arguments)));
1
aleemb
function createSomething() {
    var args = Array.prototype.concat.apply([null], arguments);
    return new (Function.prototype.bind.apply(Something, args));
}

Wenn Ihr Zielbrowser ECMAScript 5 nicht unterstützt Function.prototype.bind, der Code wird nicht funktionieren. Es ist jedoch nicht sehr wahrscheinlich, siehe Kompatibilitätstabelle .

1
user2683246

Sie können keinen Konstruktor mit einer variablen Anzahl von Argumenten aufrufen, wie Sie es mit dem Operator new möchten.

Sie können den Konstruktor leicht ändern. Anstatt:

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

Tun Sie dies stattdessen:

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

Oder wenn Sie ein Array verwenden müssen:

function Something(args) {
    var x = args[0];
    var y = args[1];
}
var obj = new Something([0, 0]);
1
Anthony Mills

@ Matthew Antwort geändert. Hier kann ich beliebig viele Parameter übergeben, um wie gewohnt zu funktionieren (kein Array). Auch 'Something' ist nicht fest programmiert in:

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
Eugen Konkov

Matthew Crumleys Lösungen in CoffeeScript:

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

oder

createSomething = (->
    F = (args) -> Something.apply this, args
    F.prototype = Something.prototype
    return -> new Something arguments
)()
1
Benjie

Jede Funktion (auch ein Konstruktor) kann eine variable Anzahl von Argumenten annehmen. Jede Funktion hat eine Variable "arguments", die mit [].slice.call(arguments) in ein Array umgewandelt werden kann.

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

Die obigen Tests erzeugen die folgende Ausgabe:

s.options === "1,2,3,4": true
z.options === "9,10,11": true
0
Wil Moore III

Während die anderen Ansätze praktikabel sind, sind sie übermäßig komplex. In Clojure erstellen Sie im Allgemeinen eine Funktion, die Typen/Datensätze instanziiert und diese Funktion als Instanziierungsmechanismus verwendet. Übersetze dies in JavaScript:

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

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

Durch diesen Ansatz vermeiden Sie die Verwendung von new, außer wie oben beschrieben. Und diese Funktion hat natürlich keine Probleme mit der Arbeit mit apply oder einer Reihe anderer funktionaler Programmierfunktionen.

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

Bei Verwendung dieses Ansatzes sind alle Ihre Typkonstruktoren (z. B. Person) Vanilla-Do-nothing-Konstruktoren. Sie übergeben einfach Argumente und weisen sie Eigenschaften mit dem gleichen Namen zu. Die haarigen Details gehen in die Konstruktorfunktion (z. B. person).

Es ist kein Problem, diese zusätzlichen Konstruktorfunktionen zu erstellen, da sie sowieso eine gute Praxis sind. Sie können praktisch sein, da Sie möglicherweise mehrere Konstruktorfunktionen mit unterschiedlichen Nuancen verwenden können.

0
Mario

Es ist auch interessant zu sehen, wie das Problem der Wiederverwendung des temporären F() -Konstruktors mithilfe von arguments.callee, Der Ersteller-/Factory-Funktion selbst, behoben wurde: http: // www. dhtmlkitchen.com/?category=/JavaScript/&date=2008/05/11/&entry=Decorator-Factory-Aspect

0
polaretto
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
user2217522

Eigentlich ist die einfachste Methode:

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
user3184743

Hier ist meine Version von createSomething:

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

Auf dieser Basis habe ich versucht, das Schlüsselwort new von JavaScript zu simulieren:

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

Ich habe es getestet und es scheint, dass es in allen Szenarien einwandfrei funktioniert. Es funktioniert auch mit nativen Konstruktoren wie Date. Hier sind einige 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
advncd

Ja, wir können, Javascript ist mehr von prototype inheritance In der Natur.

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

wenn wir ein Objekt mit "new" erstellen, dann übernimmt unser erstelltes Objekt INHERITS getAge (). Wenn wir jedoch apply(...) or call(...) zum Aufrufen von Actor verwendet haben, übergeben wir ein Objekt für "this" Aber das Objekt, das wir übergeben WON'T Erben von Actor.prototype

es sei denn, wir übergeben direkt apply oder rufen Actor.prototype auf, aber dann ... "this" verweist auf "Actor.prototype" und this.name schreibt an: Actor.prototype.name. Dies wirkt sich auf alle anderen mit Actor... Erstellten Objekte aus, da wir den Prototypen und nicht die Instanz überschreiben

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

Versuchen wir es mit 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

Durch Übergabe von Actor.prototype An Actor.call() als erstes Argument wird beim Ausführen der Actor () - Funktion this.name=name Ausgeführt, da "this" auf 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

Zurück zur ursprünglichen Frage, wie man new operator with apply Benutzt, hier ist meine Einstellung ....

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
Thalaivar

Eine überarbeitete Lösung aus @ jordancpaul's Antwort.

var applyCtor = function(ctor, args)
{
    var instance = new ctor();
    ctor.prototype.constructor.apply(instance, args);
    return instance;
}; 
0
tech-e

seit ES6 ist dies über den Spread-Operator möglich, siehe https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Apply_for_new

Diese Antwort wurde bereits im Kommentar https://stackoverflow.com/a/42027742/704981 gegeben, scheint aber von den meisten übersehen worden zu sein

0
Paul