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

当前回答

这难道不行吗?半睡半醒,没有仔细读书。

var Storage = undefined;

return ((Storage = (new Something(...))) == undefined? (undefined) : (Storage.apply(...)));

其他回答

这可能是一种低效的处理这个问题的方法,但我认为它对我来说足够简单易懂。

function createSomething(){
    // use 'new' operator to instantiate a 'Something' object
    var tmp = new Something(); 

    // If the interpreter supports [JavaScript 1.8.5][2], use 'Object.create'
    // var tmp = Object.create(Something.prototype); 

    // calling the constructor again to initialize the object
    Something.apply(tmp, arguments); 
    return tmp;
}

虽然其他方法是可行的,但它们过于复杂。在Clojure中,您通常创建一个实例化类型/记录的函数,并使用该函数作为实例化的机制。翻译成JavaScript:

function Person(surname, name){
  this.surname = surname;
  this.name = name;
}

function person(surname, name){ 
  return new Person(surname, name);
}

通过采用这种方法,可以避免使用上面描述的new except。当然,这个函数与apply或任何其他函数式编程特性一起工作都没有问题。

var doe  = _.partial(person, "Doe");
var john = doe("John");
var jane = doe("Jane");

通过使用这种方法,您的所有类型构造函数(例如Person)都是普通的、不做任何事情的构造函数。您只需传入参数并将它们分配给同名的属性。麻烦的细节放在构造函数中(例如person)。

创建这些额外的构造函数没有什么麻烦,因为它们是一种很好的实践。它们很方便,因为它们允许您潜在地拥有几个具有不同细微差别的构造函数。

我遇到了这个问题,我是这样解决的:

function instantiate(ctor) {
    switch (arguments.length) {
        case 1: return new ctor();
        case 2: return new ctor(arguments[1]);
        case 3: return new ctor(arguments[1], arguments[2]);
        case 4: return new ctor(arguments[1], arguments[2], arguments[3]);
        //...
        default: throw new Error('instantiate: too many parameters');
    }
}

function Thing(a, b, c) {
    console.log(a);
    console.log(b);
    console.log(c);
}

var thing = instantiate(Thing, 'abc', 123, {x:5});

是的,这有点丑,但它解决了问题,而且非常简单。

function FooFactory() {
    var prototype, F = function(){};

    function Foo() {
        var args = Array.prototype.slice.call(arguments),
            i;     
        for (i = 0, this.args = {}; i < args.length; i +=1) {
            this.args[i] = args[i];
        }
        this.bar = 'baz';
        this.print();

        return this;
    }

    prototype = Foo.prototype;
    prototype.print = function () {
        console.log(this.bar);
    };

    F.prototype = prototype;

    return Foo.apply(new F(), Array.prototype.slice.call(arguments));
}

var foo = FooFactory('a', 'b', 'c', 'd', {}, function (){});
console.log('foo:',foo);
foo.print();

在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替换它。绑定,我们得到如上所示的最终结果。