如何使用参数的值数组来构造对象,而不是在JavaScript中列出参数?

时间:2022-08-27 18:08:21

Is this possible? I am creating a single base factory function to drive factories of different types (but have some similarities) and I want to be able to pass arguments as an array to the base factory which then possibly creates an instance of a new object populating the arguments of the constructor of the relevant class via an array.

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

In JavaScript it's possible to use an array to call a function with multiple arguments by using the apply method:

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

It doesn't seem as if there's anyway to create an instance of an object using apply though, is there?

似乎不存在使用apply创建对象实例的方法,是吗?

Something like (this doesn't work):

比如(这行不通):

var instance = new MyClass.apply(namespace, r);

5 个解决方案

#1


15  

Try this:

试试这个:

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

All the keyword "new" does is pass in a new object to the constructor which then becomes the this variable inside the constructor function.

关键字“new”所做的一切就是将一个新对象传递给构造函数,构造函数中的构造函数就成为这个变量。

Depending upon how the constructor was written, you may have to do this:

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

var instance = {};
var returned = MyClass.apply( instance, args);
if( returned != null) {
    instance = returned;
}

Update: A comment says this doesn't work if there is a prototype. Try this.

更新:一条评论说,如果有原型的话,这是行不通的。试试这个。

function newApply(class, args) {
    function F() {
        return class.apply(this, args);
    }
    F.prototype = class.prototype;
    return new F();
}

newApply( MyClass, args);

#2


2  

Note that

请注意,

  • new myClass()
    

    without any arguments may fail, since the constructor function may rely on the existence of arguments.

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

  • myClass.apply(something, args)
    

    will fail in many cases, especially if called on native classes like Date or Number.

    在许多情况下都会失败,尤其是在调用原生类(如Date或Number)时。

I know that "eval is evil", but in this case you may want to try the following:

我知道“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;
}

Simple as that.

就这么简单。

(It works the same as Instance.New in this blog post)

它的工作原理与实例相同。新在这篇博文)

#3


1  

Hacks are hacks are hacks, but perhaps this one is a bit more elegant than some of the others, since calling syntax would be similar to what you want and you wouldn't need to modify the original classes at all:

hack是hack, hack是hack,但这个可能比其他一些更优雅,因为调用语法与您想要的类似,而且您根本不需要修改原始类:

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

Now you can do either of these to build an object:

现在你可以做任何一个来构建一个对象:

var instance1 = MyClass.build(["arg1","arg2"]);
var instance2 = new MyClass("arg1","arg2");

Granted, some may not like modifying the Function object's prototype, so you can do it this way and use it as a function instead:

当然,有些人可能不喜欢修改函数对象的原型,所以您可以这样做,并将其作为函数使用:

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

And then you would call it like so:

然后你可以这样称呼它:

var instance1 = build(MyClass, ["arg1","arg2"]);

So, I hope those are useful to someone - they allow you to leave the original constructor functions alone and get what you are after in one simple line of code (unlike the two lines you need for the currently-selected solution/workaround.

因此,我希望这些对某些人有用——它们允许您单独保留原始的构造函数,并在一个简单的代码行中获得您想要的内容(不像目前所选的解决方案/解决方案所需要的两行)。

Feedback is welcome and appreciated.

我们欢迎并感激您的反馈。


UPDATE: One other thing to note - try creating instances of the same type with these different methods and then checking to see if their constructor properties are the same - you may want that to be the case if you ever need to check the type of an object. What I mean is best illustrated by the following code:

更新:另一件需要注意的事情——尝试用这些不同的方法创建相同类型的实例,然后检查它们的构造函数是否相同——如果您需要检查对象的类型,您可能想要这样做。我的意思最好用下面的代码来说明:

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

Try that with some of the other hacks and see what happens. Ideally, you want them to be the same type.

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


CAVEAT: As noted in the comments, this will not work for those constructor functions that are created using anonymous function syntax, i.e.

注意:如注释中所提到的,这对那些使用匿名函数语法创建的构造函数不起作用,例如。

MyNamespace.SomeClass = function() { /*...*/ };

Unless you create them like this:

除非你像这样创造它们:

MyNamespace.SomeClass = function SomeClass() { /*...*/ };

The solution I provided above may or may not be useful to you, you need to understand exactly what you are doing to arrive at the best solution for your particular needs, and you need to be cognizant of what is going on to make my solution "work." If you don't understand how my solution works, spend time to figure it out.

上面我提供的解决方案可能对您有用,也可能对您没有用处,您需要确切地了解您正在做什么以达到您的特定需求的最佳解决方案,并且您需要了解如何使我的解决方案“工作”。如果你不明白我的解决方案是如何工作的,花点时间去解决它。


ALTERNATE SOLUTION: Not one to overlook other options, here is one of the other ways you could skin this cat (with similar caveats to the above approach), this one a little more esoteric:

另一种解决方案:不要忽视其他的选择,这里有另外一种方法可以让这只猫(与上面的方法类似),这个方法有点深奥:

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

Enjoy!

享受吧!

#4


0  

what about a workaround?

一个解决方案呢?

function MyClass(arg1, arg2) {

    this.init = function(arg1, arg2){
        //if(arg1 and arg2 not null) do stuff with args
    }

    init(arg1, arg2);
}

So how you can:

那么您可以:

var obj = new MyClass();
obj.apply(obj, args);

#5


0  

One possibility is to make the constructor work as a normal function call.

一种可能是让构造函数作为一个普通的函数调用工作。

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

    // normal constructor here
}

The condition on the if statement will be true if you call MyClass as a normal function (including with call/apply as long as the this argument is not a MyClass object).

如果将MyClass调用为一个普通函数(包括call/apply,只要这个参数不是MyClass对象),则if语句的条件将为真。

Now all of these are equivalent:

所有这些都是等价的

new MyClass(arg1, arg2);
MyClass(arg1, arg2);
MyClass.call(null, arg1, arg2);
MyClass.apply(null, [arg1, arg2]);

#1


15  

Try this:

试试这个:

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

All the keyword "new" does is pass in a new object to the constructor which then becomes the this variable inside the constructor function.

关键字“new”所做的一切就是将一个新对象传递给构造函数,构造函数中的构造函数就成为这个变量。

Depending upon how the constructor was written, you may have to do this:

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

var instance = {};
var returned = MyClass.apply( instance, args);
if( returned != null) {
    instance = returned;
}

Update: A comment says this doesn't work if there is a prototype. Try this.

更新:一条评论说,如果有原型的话,这是行不通的。试试这个。

function newApply(class, args) {
    function F() {
        return class.apply(this, args);
    }
    F.prototype = class.prototype;
    return new F();
}

newApply( MyClass, args);

#2


2  

Note that

请注意,

  • new myClass()
    

    without any arguments may fail, since the constructor function may rely on the existence of arguments.

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

  • myClass.apply(something, args)
    

    will fail in many cases, especially if called on native classes like Date or Number.

    在许多情况下都会失败,尤其是在调用原生类(如Date或Number)时。

I know that "eval is evil", but in this case you may want to try the following:

我知道“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;
}

Simple as that.

就这么简单。

(It works the same as Instance.New in this blog post)

它的工作原理与实例相同。新在这篇博文)

#3


1  

Hacks are hacks are hacks, but perhaps this one is a bit more elegant than some of the others, since calling syntax would be similar to what you want and you wouldn't need to modify the original classes at all:

hack是hack, hack是hack,但这个可能比其他一些更优雅,因为调用语法与您想要的类似,而且您根本不需要修改原始类:

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

Now you can do either of these to build an object:

现在你可以做任何一个来构建一个对象:

var instance1 = MyClass.build(["arg1","arg2"]);
var instance2 = new MyClass("arg1","arg2");

Granted, some may not like modifying the Function object's prototype, so you can do it this way and use it as a function instead:

当然,有些人可能不喜欢修改函数对象的原型,所以您可以这样做,并将其作为函数使用:

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

And then you would call it like so:

然后你可以这样称呼它:

var instance1 = build(MyClass, ["arg1","arg2"]);

So, I hope those are useful to someone - they allow you to leave the original constructor functions alone and get what you are after in one simple line of code (unlike the two lines you need for the currently-selected solution/workaround.

因此,我希望这些对某些人有用——它们允许您单独保留原始的构造函数,并在一个简单的代码行中获得您想要的内容(不像目前所选的解决方案/解决方案所需要的两行)。

Feedback is welcome and appreciated.

我们欢迎并感激您的反馈。


UPDATE: One other thing to note - try creating instances of the same type with these different methods and then checking to see if their constructor properties are the same - you may want that to be the case if you ever need to check the type of an object. What I mean is best illustrated by the following code:

更新:另一件需要注意的事情——尝试用这些不同的方法创建相同类型的实例,然后检查它们的构造函数是否相同——如果您需要检查对象的类型,您可能想要这样做。我的意思最好用下面的代码来说明:

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

Try that with some of the other hacks and see what happens. Ideally, you want them to be the same type.

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


CAVEAT: As noted in the comments, this will not work for those constructor functions that are created using anonymous function syntax, i.e.

注意:如注释中所提到的,这对那些使用匿名函数语法创建的构造函数不起作用,例如。

MyNamespace.SomeClass = function() { /*...*/ };

Unless you create them like this:

除非你像这样创造它们:

MyNamespace.SomeClass = function SomeClass() { /*...*/ };

The solution I provided above may or may not be useful to you, you need to understand exactly what you are doing to arrive at the best solution for your particular needs, and you need to be cognizant of what is going on to make my solution "work." If you don't understand how my solution works, spend time to figure it out.

上面我提供的解决方案可能对您有用,也可能对您没有用处,您需要确切地了解您正在做什么以达到您的特定需求的最佳解决方案,并且您需要了解如何使我的解决方案“工作”。如果你不明白我的解决方案是如何工作的,花点时间去解决它。


ALTERNATE SOLUTION: Not one to overlook other options, here is one of the other ways you could skin this cat (with similar caveats to the above approach), this one a little more esoteric:

另一种解决方案:不要忽视其他的选择,这里有另外一种方法可以让这只猫(与上面的方法类似),这个方法有点深奥:

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

Enjoy!

享受吧!

#4


0  

what about a workaround?

一个解决方案呢?

function MyClass(arg1, arg2) {

    this.init = function(arg1, arg2){
        //if(arg1 and arg2 not null) do stuff with args
    }

    init(arg1, arg2);
}

So how you can:

那么您可以:

var obj = new MyClass();
obj.apply(obj, args);

#5


0  

One possibility is to make the constructor work as a normal function call.

一种可能是让构造函数作为一个普通的函数调用工作。

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

    // normal constructor here
}

The condition on the if statement will be true if you call MyClass as a normal function (including with call/apply as long as the this argument is not a MyClass object).

如果将MyClass调用为一个普通函数(包括call/apply,只要这个参数不是MyClass对象),则if语句的条件将为真。

Now all of these are equivalent:

所有这些都是等价的

new MyClass(arg1, arg2);
MyClass(arg1, arg2);
MyClass.call(null, arg1, arg2);
MyClass.apply(null, [arg1, arg2]);