11

这可能吗?我正在创建一个基本工厂函数来驱动不同类型的工厂(但有一些相似之处),并且我希望能够将参数作为数组传递给基本工厂,然后它可能会创建一个新对象的实例来填充参数通过数组的相关类的构造函数。

在 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);
4

5 回答 5

15

试试这个:

var instance = {};
MyClass.apply( instance, r);

关键字“new”所做的只是将一个新对象传递给构造函数,然后该对象成为构造函数中的 this 变量。

根据构造函数的编写方式,您可能必须这样做:

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);
于 2009-05-01T21:35:01.543 回答
2

注意

  • new myClass()
    

    没有任何参数可能会失败,因为构造函数可能依赖于参数的存在。

  • myClass.apply(something, args)
    

    在许多情况下会失败,尤其是在 Date 或 Number 等本地类上调用时。

我知道“eval 是邪恶的”,但在这种情况下,您可能想尝试以下方法:

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中的相同)

于 2009-10-19T16:37:43.103 回答
1

Hacks are hacks are hacks,但也许这个比其他一些更优雅,因为调用语法与你想要的相似,你根本不需要修改原始类:

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

尝试使用其他一些技巧,看看会发生什么。理想情况下,您希望它们是同一类型。


警告:如评论中所述,这不适用于那些使用匿名函数语法创建的构造函数,即

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

享受!

于 2009-05-02T01:58:31.940 回答
0

解决方法呢?

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);
于 2009-05-01T21:41:28.097 回答
0

一种可能性是使构造函数像普通函数调用一样工作。

function MyClass(arg1, arg2) {
    if (!(this instanceof MyClass)) {
        return new MyClass(arg1, arg2);
    }

    // normal constructor here
}

如果您作为普通函数调用(包括/只要参数不是 a ) ,则if语句上的条件将为真。MyClasscallapplythisMyClass object

现在所有这些都是等价的:

new MyClass(arg1, arg2);
MyClass(arg1, arg2);
MyClass.call(null, arg1, arg2);
MyClass.apply(null, [arg1, arg2]);
于 2009-05-01T21:55:06.333 回答