Как я могу вызвать конструктор javascript, используя вызов или применить?
Как я могу обобщить функцию ниже, чтобы принять N аргументов? (Использование вызова или применения?)
Существует ли программный способ применения аргументов к "новому"? Я не хочу, чтобы конструктор обрабатывался как простая функция.
/**
* 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("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");
});
Ответы
Ответ 1
Попробуйте следующее:
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 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;
}
}
Ответ 2
Вот как вы это делаете:
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]);
Вызов немного проще
function callConstructor(constructor) {
var factoryFunction = constructor.bind.apply(constructor, arguments);
return new factoryFunction();
}
var d = callConstructor(Date, 2008, 10, 8, 00, 16, 34, 254);
Вы можете использовать любой из них для создания функций factory:
var dateFactory = applyToConstructor.bind(null, Date)
var d = dateFactory([2008, 10, 8, 00, 16, 34, 254]);
или
var dateFactory = callConstructor.bind(null, Date)
var d = dateFactory(2008, 10, 8, 00, 16, 34, 254);
Он будет работать с любым конструктором, а не только с встроенными или конструкторами, которые могут работать как функции (например, Date).
Однако для этого требуется функция. Прокладки, вероятно, будут работать неправильно.
Другой подход, более похожий на стиль некоторых других ответов, заключается в создании функциональной версии встроенного new
. Это не будет работать на всех встроенных устройствах (например, 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");
И теперь, конечно, вы можете сделать .apply
или .call
или .bind
на neu
как обычно.
Например:
var personFactory = neu.bind(null, Person);
var d = personFactory("Harry", "Potter");
Я чувствую, что первое решение, которое я даю, лучше, хотя, поскольку оно не зависит от вас, правильно реплицируя семантику встроенного и правильно работает со встроенными.
Ответ 3
Во всех случаях эта функция идентична new
. Это, вероятно, будет значительно медленнее, чем ответ 999. Однако, используйте его, только если вам это действительно нужно.
function applyConstructor(ctor, args) {
var a = [];
for (var i = 0; i < args.length; i++)
a[i] = 'args[' + i + ']';
return eval('new ctor(' + a.join() + ')');
}
ОБНОВЛЕНИЕ: Как только поддержка ES6 будет широко распространена, вы сможете написать следующее:
function applyConstructor(ctor, args) {
return new ctor(...args);
}
... но вам это не понадобится, потому что стандартная функция библиотеки Reflect.construct()
делает именно то, что вы ищете!
Ответ 4
Другой подход, который требует изменения фактического конструктора, который вызывается, но кажется мне более чистым, чем использование eval() или введение новой фиктивной функции в цепочке построения... Сохраняйте свою функцию conthunktor как
function conthunktor(Constructor) {
// Call the constructor
return Constructor.apply(null, Array.prototype.slice.call(arguments, 1));
}
И измените вызываемые конструкторы...
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...
}
Итак, вы можете попробовать:
var myInstance = conthunktor(MyConstructor, 1, 2, 3);
var sum = myInstance.a + myInstance.b + myInstance.c; // sum is 6
Ответ 5
Использование временного конструктора представляется лучшим решением, если Object.create
недоступно.
Если Object.create
доступно, использование его - это намного лучший вариант. В Node.js, используя Object.create
, получается гораздо более быстрый код. Здесь пример использования 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;
}
(Очевидно, что аргумент args
- это список аргументов для применения.)
У меня была часть кода, изначально использовавшая eval
для чтения части данных, созданной другим инструментом. (Да, eval
является злом.) Это создаст дерево из сотен по тысячам элементов. В основном, механизм JavaScript отвечал за разбор и выполнение кучи выражений new ...(...)
. Я преобразовал свою систему для анализа структуры JSON, что означает, что мне нужно, чтобы мой код определял, какой конструктор вызывает для каждого типа объекта в дереве. Когда я запускал новый код в своем тестовом наборе, я был удивлен, увидев резкое замедление относительно версии eval
.
- Комплект тестов с версией
eval
: 1 секунда
- Набор тестов с версией JSON с использованием временного конструктора: 5 секунд.
- Комплект тестов с версией JSON, используя
Object.create
: 1 секунду.
Набор тестов создает несколько деревьев. Я вычислил, что моя функция applytoConstructor
вызывалась около 125 000 раз, когда запускается тестовый пакет.
Ответ 6
В ECMAScript 6 вы можете использовать оператор спреда для применения конструктора с новым ключевым словом к массиву аргументов:
var dateFields = [2014, 09, 20, 19, 31, 59, 999];
var date = new Date(...dateFields);
console.log(date); // Date 2014-10-20T15:01:59.999Z
Ответ 7
В этом случае существует перерегулируемое решение. Для каждого класса, к которому вы хотите позвонить с помощью метода apply или call, вы должны позвонить до конвертацииToAllowApply ('classNameInString'); класс должен быть в том же Scoope o глобальном scoope (я не пытаюсь отправить ns.className, например...)
Имеется код:
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);