Jak mogę wywołać konstruktor javascript używając call or apply? [duplikat]
To pytanie ma już odpowiedź tutaj:
- użycie .apply() z operatorem 'new'. Czy to możliwe? 35 odpowiedzi
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");
});
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;
}
}
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.
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!
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
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
.
- zestaw testowy z wersją
eval
: 1 sekunda. - zestaw testów z wersją JSON, przy użyciu tymczasowego konstruktora: 5 sekund.
- 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.
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
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);
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