在 JavaScript 中,如何使用参数值数组构造对象,而不是列出它们?
-
03-07-2019 - |
题
这可能吗?我正在创建一个单一的基本工厂函数来驱动不同类型的工厂(但有一些相似之处),并且我希望能够将参数作为数组传递给基本工厂,然后可能会创建一个新对象的实例来填充以下参数通过数组的相关类的构造函数。
在 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);
解决方案
试试这个:
var instance = {};
MyClass.apply( instance, r);
所有关键字“new”是将新对象传递给构造函数,然后构造函数在构造函数中成为此变量。
根据构造函数的编写方式,您可能必须这样做:
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);
其他提示
注意
new myClass()
没有任何参数可能会失败,因为构造函数可能依赖于参数的存在。
myClass.apply(something, args)
在许多情况下会失败,特别是在日期或数字等本机类上调用时。
我知道“评估是邪恶的”,但在这种情况下您可能想尝试以下操作:
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
在 这篇博文)
黑客是黑客攻击,但也许这个黑客比其他人更优雅,因为调用语法类似于你想要的,你根本不需要修改原始类:
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"]);
因此,我希望这些对某些人有用 - 它们允许您单独保留原始构造函数并通过一个简单的代码行获得您所需的内容(与当前所选解决方案/解决方法所需的两行不同)
欢迎并感谢您的反馈。
更新:还有一点需要注意 - 尝试使用这些不同的方法创建相同类型的实例,然后检查它们的构造函数属性是否相同 - 如果您需要检查它们,您可能希望如此对象的类型。我的意思最好用以下代码说明:
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;
};
享受!
解决方法呢?
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);
一种可能性是使构造函数作为普通函数调用工作。
function MyClass(arg1, arg2) {
if (!(this instanceof MyClass)) {
return new MyClass(arg1, arg2);
}
// normal constructor here
}
如果将 MyClass
作为普通函数调用 if
/ ,
只要 if
语句的条件为真 this
参数不是 MyClass对象
)。
现在所有这些都是等价的:
new MyClass(arg1, arg2);
MyClass(arg1, arg2);
MyClass.call(null, arg1, arg2);
MyClass.apply(null, [arg1, arg2]);