如何使用动态数量的参数调用构造函数?

时间:2015-08-28 17:52:03

标签: javascript

我有一个构造函数,我不知道它需要多少个参数,例如:

function someCtor(a,b,c){
   var that = this;
   that.a = a;
   that.b = b;
   that.c = c;
}

我需要创建一个函数,该函数将使用动态数量的参数返回该构造函数的实例:

function makeNew(ctor, arguments){
    // this doesn't work, but it shows what I'm trying to achieve
    return new ctor.apply(arguments);
}

我想使用该函数将动态参数传递给构造函数,如下所示:

var instanceOfCtor = makeNew(someCtor, [5,6,7]);

如何实现此功能?

1 个答案:

答案 0 :(得分:5)

注意:请参阅最后的ES2015兼容性说明。

首先创建一个对象,将其底层原型设置为构造函数的prototype属性通过Object.create引用的对象,然后通过Function#apply调用构造函数:

function makeNew(ctor, arguments){
    var obj = Object.create(ctor.prototype);
    var rv = ctor.apply(obj, arguments);
    return rv && typeof rv === "object" ? rv : obj;
}

注意最后的一点点,所以我们正确地模拟new运算符:当你通过new调用构造函数时,如果它返回一个非null对象引用,最终成为new表达式的结果;如果它返回任何其他内容(或什么都没有),则new创建的对象就是结果。所以我们效仿。

即使在ES5之前的浏览器上,您也可以模仿足够的Object.create来执行此操作:

if (!Object.create) {
    Object.create = function(proto, props) {
        if (typeof props !== "undefined") {
            throw new Error("The second argument of Object.create cannot be shimmed.");
        }
        function ctor() { }
        ctor.prototype = proto;
        return new ctor;
    };
}

ES2015兼容性说明

如果您调用的构造函数是通过ES2015的class语法创建的,则上述操作无效,因为您无法以这种方式调用ES2015 class构造函数。例如:

class Example {
    constructor(a, b) {
        this.a = a;
        this.b = b;
    }
}

const e = Object.create(Example.prototype);
Example.apply(e, [1, 2]); // TypeError: Class constructor Example cannot be invoked without 'new' (or similar)

好消息是只会在ES2015 +兼容的JavaScript引擎上发生,并且只有构造函数是通过class创建的;坏消息是它可能发生。

那么你如何制作makeNew防弹?

事实证明这很容易,因为ES2015还添加了Reflect.construct,它完全符合您的要求makeNew,但它以与class构造函数兼容的方式执行和function构造函数。因此,您可以使用功能检测Reflect.construct并使用它(如果它存在(ES2015 JavaScript引擎,因此构造函数可能已使用class创建)并且如果它不存在则回退到上面(ES2015之前)引擎,周围不会有任何class构造函数:

var makeNew; // `var` because we have to avoid any ES2015+ syntax
if (typeof Reflect === "object" && Reflect && typeof Reflect.construct === "function") {
    // This is an ES2015-compatible JavaScript engine, use `Reflect.construct`
    makeNew = Reflect.construct;
} else {
    makeNew = function makeNew(ctor, arguments){
        var obj = Object.create(ctor.prototype);
        var rv = ctor.apply(obj, arguments);
        return rv && typeof rv === "object" ? rv : obj;
    };
}

这是纯粹的ES5语法,因此在ES5引擎上运行,但如果它存在则使用ES2015的Reflect.construct