将.apply()与'new'运算符一起使用.这可能吗?

Pre*_*gar 451 javascript oop inheritance constructor class

在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
Run Code Online (Sandbox Code Playgroud)

答案

从这里的回复中可以清楚地看出,没有内置的方式.apply()new运营商通话.然而,人们提出了一些非常有趣的解决方案.

我首选的解决方案是Matthew Crumley的这个解决方案(我已将其修改为通过该arguments属性):

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

    return function() {
        return new F(arguments);
    }
})();
Run Code Online (Sandbox Code Playgroud)

use*_*621 362

使用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
}
Run Code Online (Sandbox Code Playgroud)

它可以使用如下:

var s = newCall(Something, a, b, c);
Run Code Online (Sandbox Code Playgroud)

甚至直接:

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

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

这个和基于eval的解决方案是唯一始终有效的解决方案,即使使用特殊构造函数,例如Date:

var date = newCall(Date, 2012, 1);
console.log(date instanceof Date); // true
Run Code Online (Sandbox Code Playgroud)

编辑

一点解释:我们需要运行new一个需要有限数量参数的函数.该bind方法允许我们这样做:

var f = Cls.bind(anything, arg1, arg2, ...);
result = new f();
Run Code Online (Sandbox Code Playgroud)

anything参数无关紧要,因为new关键字重置了f上下文.但是,出于语法原因需要它.现在,对于bind调用:我们需要传递可变数量的参数,所以这样做的诀窍:

var f = Cls.bind.apply(Cls, [anything, arg1, arg2, ...]);
result = new f();
Run Code Online (Sandbox Code Playgroud)

让我们把它包装在一个函数中.Cls作为arugment 0传递,所以它将成为我们的anything.

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

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

function newCall(Cls /*, arg1, arg2, ... */) {
    return new (Cls.bind.apply(Cls, arguments))();
}
Run Code Online (Sandbox Code Playgroud)

最后,我们应该确保bind这才是我们真正需要的.(Cls.bind可能已被覆盖).所以替换它Function.prototype.bind,我们得到如上所述的最终结果.

  • 小修复:而不是`[null,arr]` - `[null] .concat(arr)` (4认同)
  • 你说'Function.bind`可以代替`Function.prototype.bind`是对的,我只是为了清楚起见而留下它.毕竟,可以使用任何函数:`eval.bind`可以节省更多的代码,但这真的太令人困惑了. (3认同)
  • 我发现你的解决方案非常好.但它令人困惑.cuz在第一个样本中你写了`newCall(Something,a,b,c);`其中`a`将是绑定的上下文,但在你的第二个例子中,你确实提到了上下文是没有意义的 - 所以你发送` null`.对我来说这是非常令人困惑的(你会相信我,我想了3天吗?) - 无论如何你的第一个代码(坚持你的第二个例子)需要:`function newCall(Cls){var arr = Array .prototype.slice.call(参数); arr.shift(); return new(Function.prototype.bind.apply(Cls,[null,arr]));` (3认同)
  • 有趣,但重要的是要记住IE8只支持[ECMAScript 3](http://kangax.github.com/es5-compat-table/) (2认同)

Mat*_*ley 259

这里的一个一般化的解决方案,可以调用任何构造(除了不同行为的时称作函数天然构造,如String,Number,Date等)的参数数组:

function construct(constructor, args) {
    function F() {
        return constructor.apply(this, args);
    }
    F.prototype = constructor.prototype;
    return new F();
}
Run Code Online (Sandbox Code Playgroud)

通过调用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);
    }
})();
Run Code Online (Sandbox Code Playgroud)

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

[UPDATE]

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

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

  • 一个缺点是生成的构造对象`myObj.constructor.name`属性将被设置为`F`.当查看调试器内的堆栈跟踪和向下钻取转储时,这有点糟糕,因为它们在任何地方都使用该名称. (3认同)
  • 谢谢马修.有意在飞行中打电话给关闭.虽然你的例子显示了调用函数只允许一个参数(一个args数组),但我想这可以修改为让它传递`arguments` var而不是. (2认同)
  • 这不适用于`Date`,`String`或任何其他在调用构造函数时行为不同的函数. (2认同)
  • 嗨,马修,最好修复构造函数属性aslo.答案的增强版本.http://stackoverflow.com/a/13931627/897889 (2认同)

the*_*eye 30

如果您的环境支持ECMA Script 2015的扩展运算符(...),您可以像这样使用它

function Something() {
    // init stuff
}

function createSomething() {
    return new Something(...arguments);
}
Run Code Online (Sandbox Code Playgroud)

注意:现在已发布ECMA Script 2015的规范并且大多数JavaScript引擎正在积极实现它,这将是执行此操作的首选方法.

您可以在此处查看几个主要环境中Spread操作员的支持.

  • @Wilt:JSFiddle必须在`bind.apply`中使用`[null] .concat(args)`而不仅仅是`args`.查看[固定版本](https://jsfiddle.net/yv1ck1cg/2/). (2认同)

sub*_*ack 27

假设你有一个Items构造函数,它会抛出你抛出的所有参数:

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

Items.prototype.sum = function () {
    return this.elems.reduce(function (sum, x) { return sum + x }, 0);
};
Run Code Online (Sandbox Code Playgroud)

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

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

console.log(items.sum());
Run Code Online (Sandbox Code Playgroud)

从1 + 2 + 3 + 4 = 10开始运行时打印10:

$ node t.js
10
Run Code Online (Sandbox Code Playgroud)

  • 如果你有'Object.create`可用,这是另一个好方法. (2认同)

gfa*_*ess 17

在ES6中,Reflect.construct()非常方便:

Reflect.construct(F, args)
Run Code Online (Sandbox Code Playgroud)


wuk*_*ong 9

@Matthew我认为最好修复构造函数属性.

// Invoke new operator with arbitrary arguments
// Holy Grail pattern
function invoke(constructor, args) {
    var f;
    function F() {
        // constructor returns **this**
        return constructor.apply(this, args);
    }
    F.prototype = constructor.prototype;
    f = new F();
    f.constructor = constructor;
    return f;
}
Run Code Online (Sandbox Code Playgroud)


jor*_*aul 8

@ Matthew答案的改进版本.这个表单通过将temp类存储在一个闭包中获得了一些性能上的好处,以及一个函数可以用来创建任何类的灵活性

var applyCtor = function(){
    var tempCtor = function() {};
    return function(ctor, args){
        tempCtor.prototype = ctor.prototype;
        var instance = new tempCtor();
        ctor.prototype.constructor.apply(instance,args);
        return instance;
    }
}();
Run Code Online (Sandbox Code Playgroud)

这将通过调用使用 applyCtor(class, [arg1, arg2, argn]);


Tim*_*own 7

你可以将init的东西移到一个单独Something的原型方法中:

function Something() {
    // Do nothing
}

Something.prototype.init = function() {
    // Do init stuff
};

function createSomething() {
    var s = new Something();
    s.init.apply(s, arguments);
    return s;
}

var s = createSomething(a,b,c); // 's' is an instance of Something
Run Code Online (Sandbox Code Playgroud)


Tre*_*ris 6

这个答案有点晚了,但想到任何看到这个的人都可以使用它.有一种方法可以使用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;
Run Code Online (Sandbox Code Playgroud)

对于第一个if工作的语句,testNew你必须return this;在函数的底部.以您的代码为例:

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

更新:我已经改变了我的第一个例子来加总任意数量的参数,而不仅仅是两个.


ale*_*kop 6

我刚遇到这个问题,我解决了这个问题:

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});
Run Code Online (Sandbox Code Playgroud)

是的,它有点难看,但它解决了问题,而且很简单.


小智 5

这有效!

var cls = Array; //eval('Array'); dynamically
var data = [2];
new cls(...data);
Run Code Online (Sandbox Code Playgroud)