Wie kann ich einen Javascript-Konstruktor mit call oder apply aufrufen? [duplicate]

Lesezeit: 9 Minuten

Benutzer-Avatar
verblasste Biene

Wie könnte ich die folgende Funktion verallgemeinern, um N Argumente zu nehmen? (Mit Anruf oder Bewerbung?)

Gibt es eine programmatische Möglichkeit, Argumente auf „neu“ anzuwenden? Ich möchte nicht, dass der Konstruktor wie eine einfache Funktion behandelt wird.

/**
 * This higher level function takes a constructor and arguments
 * and returns a function, which when called will return the 
 * lazily constructed value.
 * 
 * All the arguments, except the first are pased to the constructor.
 * 
 * @param {Function} constructor
 */ 

function conthunktor(Constructor) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        console.log(args);
        if (args.length === 0) {
            return new Constructor();
        }
        if (args.length === 1) {
            return new Constructor(args[0]);
        }
        if (args.length === 2) {
            return new Constructor(args[0], args[1]);
        }
        if (args.length === 3) {
            return new Constructor(args[0], args[1], args[2]);
        }
        throw("too many arguments");    
    }
}

qEinheitentest:

test("conthunktorTest", function() {
    function MyConstructor(arg0, arg1) {
        this.arg0 = arg0;
        this.arg1 = arg1;
    }
    MyConstructor.prototype.toString = function() {
        return this.arg0 + " " + this.arg1;
    }

    var thunk = conthunktor(MyConstructor, "hello", "world");
    var my_object = thunk();
    deepEqual(my_object.toString(), "hello world");
});

  • Ben Nadel schrieb darüber ausführlich.

    – Eliran Malka

    28. August 2013 um 10:39 Uhr

Benutzer-Avatar
Kybernetik

Das ist wie man es macht:

function applyToConstructor(constructor, argArray) {
    var args = [null].concat(argArray);
    var factoryFunction = constructor.bind.apply(constructor, args);
    return new factoryFunction();
}

var d = applyToConstructor(Date, [2008, 10, 8, 00, 16, 34, 254]);

Anrufen ist etwas einfacher

function callConstructor(constructor) {
    var factoryFunction = constructor.bind.apply(constructor, arguments);
    return new factoryFunction();
}

var d = callConstructor(Date, 2008, 10, 8, 00, 16, 34, 254);

Sie können beide verwenden, um Factory-Funktionen zu erstellen:

var dateFactory = applyToConstructor.bind(null, Date)
var d = dateFactory([2008, 10, 8, 00, 16, 34, 254]);

oder

var dateFactory = callConstructor.bind(null, Date)
var d = dateFactory(2008, 10, 8, 00, 16, 34, 254);

Es funktioniert mit jedem Konstruktor, nicht nur mit integrierten oder Konstruktoren, die als Funktionen fungieren können (wie Date).

Es erfordert jedoch die Ecmascript 5 .bind-Funktion. Shims werden wahrscheinlich nicht richtig funktionieren.

Ein anderer Ansatz, eher im Stil einiger anderer Antworten, besteht darin, eine Funktionsversion des integrierten zu erstellen new. Dies funktioniert nicht bei allen Builtins (wie Date).

function neu(constructor) {
    // http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.2
    var instance = Object.create(constructor.prototype);
    var result = constructor.apply(instance, Array.prototype.slice.call(arguments, 1));

    // The ECMAScript language types are Undefined, Null, Boolean, String, Number, and Object.
    return (result !== null && typeof result === 'object') ? result : instance;
}

function Person(first, last) {this.first = first;this.last = last};
Person.prototype.hi = function(){console.log(this.first, this.last);};

var p = neu(Person, "Neo", "Anderson");

Und jetzt können Sie natürlich tun .apply oder .call oder .bind an neu wie normal.

Zum Beispiel:

var personFactory = neu.bind(null, Person);
var d = personFactory("Harry", "Potter");

Ich bin der Meinung, dass die erste Lösung, die ich gebe, besser ist, da sie nicht davon abhängt, dass Sie die Semantik eines Built-Ins korrekt replizieren, und dass sie mit Built-Ins korrekt funktioniert.

  • Mich wundert, dass du dafür keine Stimme bekommen hast. Lösungen, die auf der Erstellung einer separaten Funktion und der Änderung ihres Prototyps basieren, haben den Nachteil, dass sie geändert werden constructor Feld beim Kombinieren bind mit apply erlaubt, es zu behalten.

    – mgol

    7. Februar 2013 um 19:44 Uhr

  • Das ist nett, wird aber in IE8 und darunter nicht unterstützt.

    – Dale Anderson

    25. Oktober 2013 um 7:29 Uhr

  • Ganz richtig, ie8 ist kein ecmascript5-Browser (was ich erwähne).

    – Kybernetik

    25. Oktober 2013 um 9:09 Uhr

  • @kybernetikos Mit dem Unterstrich kann eine ES4-kompatible Version erstellt werden: jsbin.com/xekaxu/1 Fühlen Sie sich frei, dies zu Ihrer Antwort hinzuzufügen, wenn Sie möchten

    – Creynders

    12. September 2014 um 7:35 Uhr

  • @rupps es ist das erste zu bindende Argument, das das ‘this’ für die Funktion sein wird, wenn es auf normale Weise aufgerufen wird. Wie wollen wir es mit nennen new, es ist nicht besonders relevant, also setze ich es dort auf null. Es gibt tatsächlich auch ein zusätzliches Argument im Aufrufbeispiel, aber da wir ein zusätzliches Argument (die Funktion selbst) am Anfang der Argumentliste haben, ist es in Ordnung, dieses einfach wiederzuverwenden. Es bedeutet, dass mit diesem Aufrufbeispiel, wenn Sie die gebundene Funktion einfach ohne aufrufen newdas this intern wird die Funktion selbst sein, aber es erspart uns, ein neues Array zu erstellen.

    – Kybernetik

    12. März 2015 um 7:54 Uhr


Benutzer-Avatar
James

Versuche dies:

function conthunktor(Constructor) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {

         var Temp = function(){}, // temporary constructor
             inst, ret; // other vars

         // Give the Temp constructor the Constructor's prototype
         Temp.prototype = Constructor.prototype;

         // Create a new instance
         inst = new Temp;

         // Call the original Constructor with the temp
         // instance as its context (i.e. its 'this' value)
         ret = Constructor.apply(inst, args);

         // If an object has been returned then return it otherwise
         // return the original instance.
         // (consistent with behaviour of the new operator)
         return Object(ret) === ret ? ret : inst;

    }
}

  • Danke, das funktioniert mit dem Testcode. Ist sein Verhalten identisch mit neu? (dh keine bösen Fallstricke zu finden.)

    – verblasste Biene

    29. Juli 2010 um 13:05 Uhr


  • Das Verhalten ist bis auf ein paar merkwürdige Funktionen wie neu identisch Date; und Proxys (eine für die nächste Version von ECMAScript vorgeschlagene Funktion, die derzeit nur in Firefox unterstützt wird – Sie können Proxys vorerst ignorieren).

    – Jason Orendorff

    12. Dezember 2011 um 17:06 Uhr

  • Schöne Lösung. Nur eine Ergänzung, Sie könnten die Temp-Funktion vermeiden und die 3 ersten Zeilen mit Object.create von ES5 neu schreiben: var inst = Object.create(Constructor.prototype);

    – Xose Lluis

    21. Januar 2013 um 23:31 Uhr


  • Es sieht so aus, als ob dies fehlschlägt XMLHttpRequest in Chrome (ich verwende Version 37.0.2062.94 unter OS X 10.9.4) ergibt TypeError: Failed to construct 'XMLHttpRequest': Please use the 'new' operator, this DOM object constructor cannot be called as a function.. Es sieht so aus, als wäre dies ein spezieller Fall für XMLHttpRequest (und höchstwahrscheinlich einige andere Objekte, die mir nicht bekannt sind). Demo: jsfiddle.net/yepygdw9

    – Rücken

    6. September 2014 um 5:02 Uhr


  • Fantastisch. Ich nehme nicht an, dass jemand eine Möglichkeit gefunden hat, dies zu erweitern, um mehr semantisches Debugging zu ermöglichen? Ich habe es versucht Temp.name = Constructor.name aber das ist illegal (name) ist schreibgeschützt. Derzeit ist das Debuggen extrem schwierig, weil alles ist Tempund ich muss Instanzen abfragen. __proto__ herauszufinden, was sie wirklich sind.

    – Barney

    11. September 2014 um 11:48 Uhr

Benutzer-Avatar
Jason Orendorff

Diese Funktion ist identisch mit new auf alle Fälle. Es wird jedoch wahrscheinlich erheblich langsamer sein als die Antwort von 999, also verwenden Sie es nur, wenn Sie es wirklich brauchen.

function applyConstructor(ctor, args) {
    var a = [];
    for (var i = 0; i < args.length; i++)
        a[i] = 'args[' + i + ']';
    return eval('new ctor(' + a.join() + ')');
}

AKTUALISIEREN: Sobald die ES6-Unterstützung weit verbreitet ist, können Sie Folgendes schreiben:

function applyConstructor(ctor, args) {
    return new ctor(...args);
}

…müssen Sie aber nicht, denn die Standardbibliothek funktioniert Reflect.construct() macht genau das was du suchst!

  • -1 für die Verwendung von eval

    – event_jr

    12. Februar 2012 um 4:38 Uhr

  • Das funktioniert auch nicht für komplexe Parameter, da Argumente in Strings umgewandelt werden: var circle = new Circle(new Point(10, 10), 10); // [object Point x=10 y=10]10

    – David Stewart

    15. Juli 2012 um 16:14 Uhr


  • Es funktioniert gut. Die Argumente werden nicht in Zeichenfolgen konvertiert. Versuch es.

    – Jason Orendorff

    17. Juli 2012 um 22:21 Uhr

  • Danke Mann, das hat für mich am besten geklappt. Und eval ist gar nicht so schlecht, wenn man weiß, was man tut, kann eval sehr nützlich sein.

    – Steffen Bremen

    3. November 2012 um 22:37 Uhr

Ein anderer Ansatz, der eine Änderung des tatsächlich aufgerufenen Konstruktors erfordert, der mir jedoch sauberer erscheint als die Verwendung von eval() oder die Einführung einer neuen Dummy-Funktion in die Konstruktionskette … Behalten Sie Ihre conthunktor-Funktion bei

function conthunktor(Constructor) {
  // Call the constructor
  return Constructor.apply(null, Array.prototype.slice.call(arguments, 1));
}

Und ändern Sie die aufgerufenen Konstruktoren …

function MyConstructor(a, b, c) {
  if(!(this instanceof MyConstructor)) {
    return new MyConstructor(a, b, c);
  }
  this.a = a;
  this.b = b;
  this.c = c;
  // The rest of your constructor...
}

Sie können also versuchen:

var myInstance = conthunktor(MyConstructor, 1, 2, 3);

var sum = myInstance.a + myInstance.b + myInstance.c; // sum is 6

  • Nett!

    – Mark K. Cowan

    22. Oktober 2014 um 11:17 Uhr

Die Verwendung eines temporären Konstruktors scheint die beste Lösung zu sein, wenn Object.create ist nicht verfügbar.

Wenn Object.create verfügbar ist, dann ist die Verwendung eine viel bessere Option. Verwenden Sie auf Node.js Object.create führt zu viel schnellerem Code. Hier ist ein Beispiel dafür, wie Object.create kann verwendet werden:

function applyToConstructor(ctor, args) {
    var new_obj = Object.create(ctor.prototype);
    var ctor_ret = ctor.apply(new_obj, args);

    // Some constructors return a value; make sure to use it!
    return ctor_ret !== undefined ? ctor_ret: new_obj;
}

(Natürlich ist die args Argument ist eine Liste von anzuwendenden Argumenten.)

Ich hatte ein Stück Code, das ursprünglich verwendet wurde eval um ein Datenelement zu lesen, das von einem anderen Tool erstellt wurde. (Ja, eval ist böse.) Dies würde einen Baum mit Hunderten bis Tausenden von Elementen instanziieren. Grundsätzlich war die JavaScript-Engine für das Parsen und Ausführen einer Reihe von new ...(...) Ausdrücke. Ich habe mein System so konvertiert, dass es eine JSON-Struktur analysiert, was bedeutet, dass mein Code bestimmen muss, welcher Konstruktor für jeden Objekttyp im Baum aufgerufen werden soll. Als ich den neuen Code in meiner Testsuite ausführte, war ich überrascht, eine dramatische Verlangsamung relativ zum zu sehen eval Ausführung.

  1. Testsuite mit eval Ausführung: 1 Sekunde.
  2. Testsuite mit JSON-Version unter Verwendung des temporären Konstruktors: 5 Sekunden.
  3. Testsuite mit JSON-Version unter Verwendung von Object.create: 1 Sekunde.

Die Testsuite erstellt mehrere Bäume. Ich habe meine berechnet applytoConstructor Die Funktion wurde beim Ausführen der Testsuite etwa 125.000 Mal aufgerufen.

Benutzer-Avatar
Danyg

Für diesen Fall gibt es eine wiederverwendbare Lösung. Für jede Klasse, die Sie mit der Methode apply oder call aufrufen möchten, müssen Sie vorher convertToAllowApply(‘classNameInString’) aufrufen; Die Klasse muss sich im selben Scoope oder globalen Scoope befinden (ich versuche zum Beispiel nicht, ns.className zu senden …)

Da ist der Code:

function convertToAllowApply(kName){
    var n = '\n', t="\t";
    var scrit="var oldKlass = " + kName + ';' + n +
        kName + '.prototype.__Creates__ = oldKlass;' + n +

        kName + ' = function(){' + n +
            t + 'if(!(this instanceof ' + kName + ')){'+ n +
                t + t + 'obj = new ' + kName + ';'+ n +
                t + t + kName + '.prototype.__Creates__.apply(obj, arguments);'+ n +
                t + t + 'return obj;' + n +
            t + '}' + n +
        '}' + n +
        kName + '.prototype = oldKlass.prototype;';

    var convert = new Function(scrit);

    convert();
}

// USE CASE:

myKlass = function(){
    this.data = Array.prototype.slice.call(arguments,0);
    console.log('this: ', this);
}

myKlass.prototype.prop = 'myName is myKlass';
myKlass.prototype.method = function(){
    console.log(this);
}

convertToAllowApply('myKlass');

var t1 = myKlass.apply(null, [1,2,3]);
console.log('t1 is: ', t1);

1159620cookie-checkWie kann ich einen Javascript-Konstruktor mit call oder apply aufrufen? [duplicate]

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy