Как я могу построить объект, используя массив значений для параметров, а не перечислять их в JavaScript?
Возможно ли это? Я создаю единую базовую функцию factory для управления фабриками разных типов (но имеют некоторые сходства), и я хочу иметь возможность передавать аргументы в виде массива на базу factory, которая затем, возможно, создает экземпляр нового объекта заполняя аргументы конструктора соответствующего класса через массив.
В JavaScript можно использовать массив для вызова функции с несколькими аргументами с помощью метода apply:
namespace.myFunc = function(arg1, arg2) { //do something; }
var result = namespace.myFunc("arg1","arg2");
//this is the same as above:
var r = [ "arg1","arg2" ];
var result = myFunc.apply(namespace, r);
Не похоже, что в любом случае для создания экземпляра объекта, использующего apply, есть?
Что-то вроде (это не работает):
var instance = new MyClass.apply(namespace, r);
Ответы
Ответ 1
Попробуйте следующее:
var instance = {};
MyClass.apply( instance, r);
Все ключевое слово "new" is is pass в новом объекте для конструктора, который затем становится этой переменной внутри функции конструктора.
В зависимости от того, как был написан конструктор, вам может потребоваться следующее:
var instance = {};
var returned = MyClass.apply( instance, args);
if( returned != null) {
instance = returned;
}
Обновление: комментарий говорит, что это не работает, если есть прототип. Попробуйте это.
function newApply(class, args) {
function F() {
return class.apply(this, args);
}
F.prototype = class.prototype;
return new F();
}
newApply( MyClass, args);
Ответ 2
Обратите внимание, что
-
new myClass()
без каких-либо аргументов может произойти сбой, так как функция-конструктор может полагаться на существование аргументов.
-
myClass.apply(something, args)
не удастся во многих случаях, особенно если вызывается в таких классах, как Date или Number.
Я знаю, что "eval is evil", но в этом случае вы можете попробовать следующее:
function newApply(Cls, args) {
var argsWrapper = [];
for (var i = 0; i < args.length; i++) {
argsWrapper.push('args[' + i + ']');
}
eval('var inst = new Cls(' + argsWrapper.join(',') + ');' );
return inst;
}
Просто как это.
(Он работает так же, как Instance.New
в этот пост в блоге)
Ответ 3
Хаки - хаки - это хаки, но, возможно, это немного более элегантно, чем некоторые другие, поскольку синтаксис вызова будет похож на то, что вы хотите, и вам вообще не нужно будет изменять исходные классы:
Function.prototype.build = function(parameterArray) {
var functionNameResults = (/function (.{1,})\(/).exec(this.toString());
var constructorName = (functionNameResults && functionNameResults.length > 1) ? functionNameResults[1] : "";
var builtObject = null;
if(constructorName != "") {
var parameterNameValues = {}, parameterNames = [];
for(var i = 0; i < parameterArray.length; i++) {
var parameterName = ("p_" + i);
parameterNameValues[parameterName] = parameterArray[i];
parameterNames.push(("parameterNameValues." + parameterName));
}
builtObject = (new Function("parameterNameValues", "return new " + constructorName + "(" + parameterNames.join(",") + ");"))(parameterNameValues);
}
return builtObject;
};
Теперь вы можете сделать любой из них для создания объекта:
var instance1 = MyClass.build(["arg1","arg2"]);
var instance2 = new MyClass("arg1","arg2");
Конечно, некоторым может не понравиться модифицировать прототип объекта Function, поэтому вы можете сделать это таким образом и использовать его как функцию вместо:
function build(constructorFunction, parameterArray) {
var functionNameResults = (/function (.{1,})\(/).exec(constructorFunction.toString());
var constructorName = (functionNameResults && functionNameResults.length > 1) ? functionNameResults[1] : "";
var builtObject = null;
if(constructorName != "") {
var parameterNameValues = {}, parameterNames = [];
for(var i = 0; i < parameterArray.length; i++) {
var parameterName = ("p_" + i);
parameterNameValues[parameterName] = parameterArray[i];
parameterNames.push(("parameterNameValues." + parameterName));
}
builtObject = (new Function("parameterNameValues", "return new " + constructorName + "(" + parameterNames.join(",") + ");"))(parameterNameValues);
}
return builtObject;
};
И тогда вы будете называть его так:
var instance1 = build(MyClass, ["arg1","arg2"]);
Итак, я надеюсь, что они полезны для кого-то - они позволяют вам оставлять только исходные функции-конструкторы и получать то, что вам нужно, в одной простой строке кода (в отличие от двух строк, которые вам нужны для выбранного в настоящий момент решения/обходного пути.
Обратная связь приветствуется и ценится.
UPDATE: Еще одна вещь, которую нужно отметить: попробуйте создать экземпляры одного и того же типа с помощью этих разных методов, а затем проверив, чтобы их свойства были одинаковыми - вы можете захотеть, чтобы это было так, если вам нужно проверить тип объекта. То, что я имею в виду, лучше всего иллюстрируется следующим кодом:
function Person(firstName, lastName) {
this.FirstName = firstName;
this.LastName = lastName;
}
var p1 = new Person("John", "Doe");
var p2 = Person.build(["Sara", "Lee"]);
var areSameType = (p1.constructor == p2.constructor);
Попробуйте с некоторыми другими хаками и посмотрите, что произойдет. В идеале вы хотите, чтобы они были одного типа.
CAVEAT: Как отмечено в комментариях, это не будет работать для тех функций конструктора, которые создаются с использованием синтаксиса анонимных функций, то есть
MyNamespace.SomeClass = function() { /*...*/ };
Если вы не создадите их так:
MyNamespace.SomeClass = function SomeClass() { /*...*/ };
Решение, которое я изложил выше, может или не может быть полезным для вас, вам нужно точно понять, что вы делаете, чтобы достичь наилучшего решения для ваших конкретных потребностей, и вам нужно знать, что происходит, чтобы сделать мое решение "работает". Если вы не понимаете, как работает мое решение, потратьте время, чтобы понять это.
АЛЬТЕРНАТИВНОЕ РЕШЕНИЕ: не один, чтобы игнорировать другие варианты, вот один из других способов, которыми вы могли бы скрыть эту кошку (с аналогичными предостережениями к вышеуказанному подходу), это немного более эзотерическое:
function partial(func/*, 0..n args */) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var allArguments = args.concat(Array.prototype.slice.call(arguments));
return func.apply(this, allArguments);
};
}
Function.prototype.build = function(args) {
var constructor = this;
for(var i = 0; i < args.length; i++) {
constructor = partial(constructor, args[i]);
}
constructor.prototype = this.prototype;
var builtObject = new constructor();
builtObject.constructor = this;
return builtObject;
};
Наслаждайтесь!
Ответ 4
как обходной путь?
function MyClass(arg1, arg2) {
this.init = function(arg1, arg2){
//if(arg1 and arg2 not null) do stuff with args
}
init(arg1, arg2);
}
Итак, как вы можете:
var obj = new MyClass();
obj.apply(obj, args);
Ответ 5
Одна из возможностей - заставить конструктор работать как обычный вызов функции.
function MyClass(arg1, arg2) {
if (!(this instanceof MyClass)) {
return new MyClass(arg1, arg2);
}
// normal constructor here
}
Условие в операторе if
будет истинным, если вы вызываете MyClass
как нормальную функцию (включая call
/apply
, если аргумент this
не является MyClass object
).
Теперь все они эквивалентны:
new MyClass(arg1, arg2);
MyClass(arg1, arg2);
MyClass.call(null, arg1, arg2);
MyClass.apply(null, [arg1, arg2]);