在JavaScript中,我想创建一个对象实例(通过new操作符),但将任意数量的参数传递给构造函数。这可能吗?

我想做的是这样的(但下面的代码不起作用):

function Something(){
    // init stuff
}
function createSomething(){
    return new Something.apply(null, arguments);
}
var s = createSomething(a,b,c); // 's' is an instance of Something

这个问题的答案

从这里的响应可以清楚地看出,没有内置的方法可以使用new操作符调用.apply()。然而,人们对这个问题提出了许多非常有趣的解决方案。

我更喜欢的解决方案是来自Matthew Crumley的这个(我修改了它来传递arguments属性):

var createSomething = (function() {
    function F(args) {
        return Something.apply(this, args);
    }
    F.prototype = Something.prototype;

    return function() {
        return new F(arguments);
    }
})();

当前回答

假设你有一个Items构造函数,它吸收了你给它的所有参数:

function Items () {
    this.elems = [].slice.call(arguments);
}

Items.prototype.sum = function () {
    return this.elems.reduce(function (sum, x) { return sum + x }, 0);
};

你可以用Object.create()创建一个实例,然后用.apply()创建该实例:

var items = Object.create(Items.prototype);
Items.apply(items, [ 1, 2, 3, 4 ]);

console.log(items.sum());

当运行时输出10,因为1 + 2 + 3 + 4 == 10:

$ node t.js
10

其他回答

在ECMAScript5的Function.prototype.bind中,事情变得非常清晰:

function newCall(Cls) {
    return new (Function.prototype.bind.apply(Cls, arguments));
    // or even
    // return new (Cls.bind.apply(Cls, arguments));
    // if you know that Cls.bind has not been overwritten
}

它可以这样使用:

var s = newCall(Something, a, b, c);

或者直接说:

var s = new (Function.prototype.bind.call(Something, null, a, b, c));

var s = new (Function.prototype.bind.apply(Something, [null, a, b, c]));

这和基于求值的解决方案是唯一总是有效的,即使使用像Date这样的特殊构造函数:

var date = newCall(Date, 2012, 1);
console.log(date instanceof Date); // true

edit

稍微解释一下: 我们需要在一个接受有限数量参数的函数上运行new。bind方法允许我们这样做:

var f = Cls.bind(anything, arg1, arg2, ...);
result = new f();

any形参并不重要,因为new关键字会重置f的上下文。但是,由于语法原因,它是必需的。现在,对于bind调用:我们需要传递一个可变数量的参数,这样就可以了:

var f = Cls.bind.apply(Cls, [anything, arg1, arg2, ...]);
result = new f();

让我们把它包装在一个函数中。Cls被作为参数0传递,所以它是我们的任何东西。

function newCall(Cls /*, arg1, arg2, ... */) {
    var f = Cls.bind.apply(Cls, arguments);
    return new f();
}

实际上,临时变量f根本不需要:

function newCall(Cls /*, arg1, arg2, ... */) {
    return new (Cls.bind.apply(Cls, arguments))();
}

最后,我们应该确保bind确实是我们所需要的。(Cls。Bind可能已经被覆盖)。所以用function。prototype替换它。绑定,我们得到如上所示的最终结果。

这个工作!

var cls = Array; //eval('Array'); dynamically
var data = [2];
new cls(...data);

这是一个通用的解决方案,可以调用任何构造函数(除了本机构造函数,当作为函数调用时行为不同,如String, Number, Date等)与参数数组:

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

通过调用construct(Class,[1,2,3])创建的对象将与用new Class(1,2,3)创建的对象相同。

您还可以创建一个更具体的版本,这样就不必每次都传递构造函数。这也稍微更有效,因为它不需要每次调用时都创建内部函数的新实例。

var createSomething = (function() {
    function F(args) {
        return Something.apply(this, args);
    }
    F.prototype = Something.prototype;

    return function(args) {
        return new F(args);
    }
})();

这样创建和调用外部匿名函数的原因是为了防止函数F污染全局命名空间。它有时被称为模块模式。

(更新)

对于那些想在TypeScript中使用它的人,因为如果F返回任何东西,TS会给出一个错误:

function construct(constructor, args) {
    function F() : void {
        constructor.apply(this, args);
    }
    F.prototype = constructor.prototype;
    return new F();
}

这个答案有点晚了,但我想任何看到这个的人都可以使用它。有一种方法可以使用apply返回一个新对象。尽管它需要对对象声明做一点更改。

function testNew() {
    if (!( this instanceof arguments.callee ))
        return arguments.callee.apply( new arguments.callee(), arguments );
    this.arg = Array.prototype.slice.call( arguments );
    return this;
}

testNew.prototype.addThem = function() {
    var newVal = 0,
        i = 0;
    for ( ; i < this.arg.length; i++ ) {
        newVal += this.arg[i];
    }
    return newVal;
}

testNew( 4, 8 ) === { arg : [ 4, 8 ] };
testNew( 1, 2, 3, 4, 5 ).addThem() === 15;

为了让第一个if语句在testNew中工作,你必须返回这个;在函数的底部。以代码为例:

function Something() {
    // init stuff
    return this;
}
function createSomething() {
    return Something.apply( new Something(), arguments );
}
var s = createSomething( a, b, c );

更新:我改变了我的第一个例子的总和参数的数量,而不是只有两个。

感谢这里的帖子,我这样使用它:

SomeClass = function(arg1, arg2) {
    // ...
}

ReflectUtil.newInstance('SomeClass', 5, 7);

和实现:

/**
 * @param strClass:
 *          class name
 * @param optionals:
 *          constructor arguments
 */
ReflectUtil.newInstance = function(strClass) {
    var args = Array.prototype.slice.call(arguments, 1);
    var clsClass = eval(strClass);
    function F() {
        return clsClass.apply(this, args);
    }
    F.prototype = clsClass.prototype;
    return new F();
};