Jak mogę wywołać konstruktor javascript używając call or apply? [duplikat]

To pytanie ma już odpowiedź tutaj:

Jak mogę uogólnić poniższą funkcję, aby przyjąć N argumentów? (Za pomocą zadzwoń lub aplikuj?)

Czy istnieje programowy sposób na zastosowanie argumentów do 'new'? Nie chcę, żeby konstruktor był traktowany jak zwykły funkcja.

/**
 * 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");    
    }
}

QUnit test:

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");
});
Author: ROMANIA_engineer, 2010-07-29

7 answers

Spróbuj tego:

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;

    }
}
 49
Author: James,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-29 13:11:43

Tak to się robi:

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

Połączenie jest nieco łatwiejsze

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

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

Możesz użyć jednej z nich do utworzenia funkcji fabrycznych:

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

Lub

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

Będzie działać z dowolnym konstruktorem, a nie tylko wbudowanymi lub konstruktorami, które mogą podwoić się jako funkcje (jak Date).

Jednak wymaga Ecmascript 5 .funkcja bind. Podkładki dystansowe prawdopodobnie nie będą działać poprawnie.

Inne podejście, bardziej w stylu innych odpowiedzi polega na utworzeniu wbudowanej wersji funkcji new. To nie będzie działać na wszystkich wbudowanych (takich jak data).

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

A teraz, oczywiście, możesz zrobić .apply lub .call lub .bind na neu jak zwykle.

Na przykład:

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

Wydaje mi się, że pierwsze rozwiązanie, które podam, jest lepsze, ponieważ nie zależy od poprawnego odwzorowania semantyki builtina i działa poprawnie z builtins.

 92
Author: kybernetikos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-07 22:12:22

Ta funkcja jest identyczna z new we wszystkich przypadkach. Prawdopodobnie będzie to znacznie wolniejsze niż odpowiedź 999, więc używaj go tylko wtedy, gdy naprawdę go potrzebujesz.

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

UPDATE: gdy wsparcie dla ES6 będzie powszechne, będziesz mógł napisać to:

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

...ale nie będziesz musiał, ponieważ standardowa funkcja biblioteczna Reflect.construct() robi dokładnie to, czego szukasz!

 15
Author: Jason Orendorff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-12 18:38:20

Inne podejście, które wymaga modyfikacji wywołanego konstruktora, ale wydaje mi się czystsze niż użycie eval() lub wprowadzenie nowej funkcji dummy w łańcuchu budowy... Zachowaj funkcję conthunktor jak

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

I zmodyfikować wywołane konstruktory...

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

Więc możesz spróbować:

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

var sum = myInstance.a + myInstance.b + myInstance.c; // sum is 6
 4
Author: Fuerteflojo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-04 15:17:55

Użycie tymczasowego konstruktora wydaje się być najlepszym rozwiązaniem, jeśli Object.create nie jest dostępne.

Jeśli Object.create jest dostępny, to Korzystanie z niego jest znacznie lepszą opcją. Na Węźle.js, użycie Object.create powoduje znacznie szybszy kod. Oto przykład użycia Object.create:

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

(oczywiście, argument args jest listą argumentów do zastosowania.)

Miałem fragment kodu, który pierwotnie używał eval do odczytu danych utworzonych przez inne narzędzie. (Tak, eval jest złem.) Tworzyłoby to drzewo złożone z setek do tysięcy elementów. Zasadniczo silnik JavaScript był odpowiedzialny za parsowanie i wykonywanie kilku wyrażeń new ...(...). Przekonwertowałem mój system do analizy struktury JSON, co oznacza, że mój kod musi określać, który konstruktor wywołać dla każdego typu obiektu w drzewie. Kiedy uruchomiłem nowy kod w moim zestawie testowym, byłem zaskoczony, widząc dramatyczne spowolnienie w stosunku do wersji eval.

  1. zestaw testowy z wersją eval: 1 sekunda.
  2. zestaw testów z wersją JSON, przy użyciu tymczasowego konstruktora: 5 sekund.
  3. pakiet testowy z wersją JSON, za pomocą Object.create: 1 drugi.

Zestaw testów tworzy wiele drzew. Obliczyłem, że moja applytoConstructor funkcja została wywołana około 125,000 razy, gdy pakiet testowy jest uruchomiony.

 3
Author: Louis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-13 16:59:47

W ECMAScript 6 można użyć operatora spread, aby zastosować konstruktor ze słowem kluczowym new do tablicy argumentów:

var dateFields = [2014, 09, 20, 19, 31, 59, 999];
var date = new Date(...dateFields);
console.log(date);  // Date 2014-10-20T15:01:59.999Z
 3
Author: Ghasem Kiani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-20 15:09:08

W tym przypadku istnieje rozwiązanie, które można ponownie wykorzystać. Dla każdej klasy, którą chcesz wywołać za pomocą metody apply lub call, musisz wywołać wcześniej, aby convertToAllowApply ('classNameInString'); Klasa musi być w tym samym Scoope o global scoope (nie próbuję wysyłać ns.na przykład nazwa klasy...)

Jest kod:

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);
 1
Author: danyg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-03 12:01:30