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

时间:2009-10-22 12:15:09

标签: javascript oop class inheritance constructor

在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

答案

根据此处的回复,很明显没有内置方法可以使用.apply()运算符调用new。然而,人们提出了一些非常有趣的解决方案。

我首选的解决方案是this one from Matthew Crumley(我已修改它以传递arguments属性):

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

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

35 个答案:

答案 0 :(得分:348)

使用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这样的特殊构造函数,这个和eval-based solution也是唯一有效的:

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

修改

一点解释: 我们需要在一个带有有限数量参数的函数上运行newbind方法允许我们这样做:

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

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

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

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

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.bind替换它,我们得到上面的最终结果。

答案 1 :(得分:259)

这是一个通用解决方案,可以调用任何构造函数(除了本机构造函数,在调用函数时表现不同,如StringNumberDate等),其数组为参数:

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不会污染全局命名空间。它有时被称为模块模式。

<强> [UPDATE]

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

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

答案 2 :(得分:29)

如果您的环境支持ECMA Script 2015's spread operator (...),您可以像这样使用它

function Something() {
    // init stuff
}

function createSomething() {
    return new Something(...arguments);
}

注意:既然已发布ECMA Script 2015的规范并且大多数JavaScript引擎正在积极实施它,那么这将是首选方法。

您可以在少数主要环境here中查看Spread运营商的支持。

答案 3 :(得分:27)

假设您有一个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());

当从1 + 2 + 3 + 4 == 10:

开始打印10时
$ node t.js
10

答案 4 :(得分:14)

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

Reflect.construct(F, args)

答案 5 :(得分: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;
}

答案 6 :(得分: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;
    }
}();

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

来使用

答案 7 :(得分:7)

您可以将初始化内容移动到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

答案 8 :(得分: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;

要在if中使用的第一个testNew语句,您必须在函数底部return this;。以您的代码为例:

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

更新:我已经改变了我的第一个例子来对任意数量的参数求和,而不仅仅是两个。

答案 9 :(得分: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});

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

答案 10 :(得分:4)

如果您对基于eval的解决方案感兴趣

function createSomething() {
    var q = [];
    for(var i = 0; i < arguments.length; i++)
        q.push("arguments[" + i + "]");
    return eval("new Something(" + q.join(",") + ")");
}

答案 11 :(得分:3)

另请参阅CoffeeScript如何做到这一点。

s = new Something([a,b,c]...)

变为:

var s;
s = (function(func, args, ctor) {
  ctor.prototype = func.prototype;
  var child = new ctor, result = func.apply(child, args);
  return Object(result) === result ? result : child;
})(Something, [a, b, c], function(){});

答案 12 :(得分:3)

这有效!

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

答案 13 :(得分:2)

解决方案不含 ES6或polyfills:

var obj = _new(Demo).apply(["X", "Y", "Z"]);


function _new(constr)
{
    function createNamedFunction(name)
    {
        return (new Function("return function " + name + "() { };"))();
    }

    var func = createNamedFunction(constr.name);
    func.prototype = constr.prototype;
    var self = new func();

    return { apply: function(args) {
        constr.apply(self, args);
        return self;
    } };
}

function Demo()
{
    for(var index in arguments)
    {
        this['arg' + (parseInt(index) + 1)] = arguments[index];
    }
}
Demo.prototype.tagged = true;


console.log(obj);
console.log(obj.tagged);


输出

演示{arg1:&#34; X&#34;,arg2:&#34; Y&#34;,arg3:&#34; Z&#34;}


......或者&#34;更短&#34;方式:

var func = new Function("return function " + Demo.name + "() { };")();
func.prototype = Demo.prototype;
var obj = new func();

Demo.apply(obj, ["X", "Y", "Z"]);


修改
我认为这可能是一个很好的解决方案:

this.forConstructor = function(constr)
{
    return { apply: function(args)
    {
        let name = constr.name.replace('-', '_');

        let func = (new Function('args', name + '_', " return function " + name + "() { " + name + "_.apply(this, args); }"))(args, constr);
        func.constructor = constr;
        func.prototype = constr.prototype;

        return new func(args);
    }};
}

答案 14 :(得分:2)

此构造函数方法可以使用和不使用new关键字:

function Something(foo, bar){
  if (!(this instanceof Something)){
    var obj = Object.create(Something.prototype);
    return Something.apply(obj, arguments);
  }
  this.foo = foo;
  this.bar = bar;
  return this;
}

它假定支持Object.create,但如果您支持旧浏览器,则可以随时填充。 See the support table on MDN here

这里是JSBin to see it in action with console output

答案 15 :(得分:1)

CoffeeScript中的

Matthew Crumley's solutions

construct = (constructor, args) ->
    F = -> constructor.apply this, args
    F.prototype = constructor.prototype
    new F

createSomething = (->
    F = (args) -> Something.apply this, args
    F.prototype = Something.prototype
    return -> new Something arguments
)()

答案 16 :(得分:1)

这个单行应该这样做:

new (Function.prototype.bind.apply(Something, [null].concat(arguments)));

答案 17 :(得分:1)

function createSomething() {
    var args = Array.prototype.concat.apply([null], arguments);
    return new (Function.prototype.bind.apply(Something, args));
}

如果目标浏览器不支持ECMAScript 5 Function.prototype.bind,则代码将无效。但这不太可能,请参阅compatibilty table

答案 18 :(得分:1)

修改了@Matthew的答案。在这里,我可以传递任意数量的参数来照常运行(不是数组)。还有&#39; Something&#39;并没有硬编码:

function createObject( constr ) {   
  var args =  arguments;
  var wrapper =  function() {  
    return constr.apply( this, Array.prototype.slice.call(args, 1) );
  }

  wrapper.prototype =  constr.prototype;
  return  new wrapper();
}


function Something() {
    // init stuff
};

var obj1 =     createObject( Something, 1, 2, 3 );
var same =     new Something( 1, 2, 3 );

答案 19 :(得分:1)

您无法使用new运算符调用具有可变数量参数的构造函数。

您可以做的是稍微更改构造函数。而不是:

function Something() {
    // deal with the "arguments" array
}
var obj = new Something.apply(null, [0, 0]);  // doesn't work!

请改为:

function Something(args) {
    // shorter, but will substitute a default if args.x is 0, false, "" etc.
    this.x = args.x || SOME_DEFAULT_VALUE;

    // longer, but will only put in a default if args.x is not supplied
    this.x = (args.x !== undefined) ? args.x : SOME_DEFAULT_VALUE;
}
var obj = new Something({x: 0, y: 0});

或者如果你必须使用数组:

function Something(args) {
    var x = args[0];
    var y = args[1];
}
var obj = new Something([0, 0]);

答案 20 :(得分:0)

制作一个匿名原型,并使用参数将Something原型应用于该原型,然后创建该匿名原型的新实例。这样做的一个缺点是它不会通过s instanceof Something检查,尽管它是相同的,但它基本上是克隆的一个实例。

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

答案 21 :(得分:0)

是的,我们可以,javascript本质上更多是prototype inheritance

function Actor(name, age){
  this.name = name;
  this.age = age;
}

Actor.prototype.name = "unknown";
Actor.prototype.age = "unknown";

Actor.prototype.getName = function() {
    return this.name;
};

Actor.prototype.getAge = function() {
    return this.age;
};

当我们创建一个带有“new”的对象时,我们创建的对象INHERITS getAge(),但是如果我们使用apply(...) or call(...)来调用Actor,那么我们将为{传递一个对象{1}}但我们传递的对象"this"继承自WON'T

除非,我们直接传递apply或调用Actor.prototype但是......“this”将指向“Actor.prototype”而this.name将写入:Actor.prototype。因此影响用Actor.prototype.name创建的所有其他对象,因为我们覆盖了原型而不是实例

Actor...

让我们试试var rajini = new Actor('Rajinikanth', 31); console.log(rajini); console.log(rajini.getName()); console.log(rajini.getAge()); var kamal = new Actor('kamal', 18); console.log(kamal); console.log(kamal.getName()); console.log(kamal.getAge());

apply

通过将var vijay = Actor.apply(null, ["pandaram", 33]); if (vijay === undefined) { console.log("Actor(....) didn't return anything since we didn't call it with new"); } var ajith = {}; Actor.apply(ajith, ['ajith', 25]); console.log(ajith); //Object {name: "ajith", age: 25} try { ajith.getName(); } catch (E) { console.log("Error since we didn't inherit ajith.prototype"); } console.log(Actor.prototype.age); //Unknown console.log(Actor.prototype.name); //Unknown 传递给Actor.prototype作为第一个参数,当运行Actor()函数时,它会执行Actor.call(),因为“this”将指向this.name=nameActor.prototype

this.name=name; means Actor.prototype.name=name;

回到原始问题如何使用var simbhu = Actor.apply(Actor.prototype, ['simbhu', 28]); if (simbhu === undefined) { console.log("Still undefined since the function didn't return anything."); } console.log(Actor.prototype.age); //simbhu console.log(Actor.prototype.name); //28 var copy = Actor.prototype; var dhanush = Actor.apply(copy, ["dhanush", 11]); console.log(dhanush); console.log("But now we've corrupted Parent.prototype in order to inherit"); console.log(Actor.prototype.age); //11 console.log(Actor.prototype.name); //dhanush ,这是我的看法......

new operator with apply

答案 22 :(得分:0)

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

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

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

通过采用这种方法,您可以避免使用new,除非如上所述。当然,这个函数在使用apply或任何其他函数式编程功能时没有任何问题。

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

通过使用此方法,所有类型构造函数(例如Person)都是vanilla,do-nothing构造函数。您只需传入参数并将它们分配给同名的属性。毛茸茸的细节包含在构造函数中(例如person)。

创建这些额外的构造函数很麻烦,因为无论如何它们都是一个很好的实践。它们可以很方便,因为它们允许您具有几个具有不同细微差别的构造函数。

答案 23 :(得分:0)

因为ES6可以通过Spread运算符实现,所以请参阅https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator#Apply_for_new

这个答案已经在评论https://stackoverflow.com/a/42027742/7049810中给出,但大多数人似乎都错过了

答案 24 :(得分:0)

@ jordancpaul回答的修订解决方案。

var applyCtor = function(ctor, args)
{
    var instance = new ctor();
    ctor.prototype.constructor.apply(instance, args);
    return instance;
}; 

答案 25 :(得分:0)

这是我的createSomething版本:

function createSomething() {
    var obj = {};
    obj = Something.apply(obj, arguments) || obj;
    obj.__proto__ = Something.prototype; //Object.setPrototypeOf(obj, Something.prototype); 
    return o;
}

基于此,我尝试模拟JavaScript的new关键字:

//JavaScript 'new' keyword simulation
function new2() {
    var obj = {}, args = Array.prototype.slice.call(arguments), fn = args.shift();
    obj = fn.apply(obj, args) || obj;
    Object.setPrototypeOf(obj, fn.prototype); //or: obj.__proto__ = fn.prototype;
    return obj;
}

我对它进行了测试,似乎它适用于所有场景。它也适用于Date等本机构造函数。以下是一些测试:

//test
new2(Something);
new2(Something, 1, 2);

new2(Date);         //"Tue May 13 2014 01:01:09 GMT-0700" == new Date()
new2(Array);        //[]                                  == new Array()
new2(Array, 3);     //[undefined × 3]                     == new Array(3)
new2(Object);       //Object {}                           == new Object()
new2(Object, 2);    //Number {}                           == new Object(2)
new2(Object, "s");  //String {0: "s", length: 1}          == new Object("s")
new2(Object, true); //Boolean {}                          == new Object(true)

答案 26 :(得分:0)

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

答案 27 :(得分:0)

任何函数(甚至构造函数)都可以使用可变数量的参数。每个函数都有一个“arguments”变量,可以使用[].slice.call(arguments)强制转换为数组。

function Something(){
  this.options  = [].slice.call(arguments);

  this.toString = function (){
    return this.options.toString();
  };
}

var s = new Something(1, 2, 3, 4);
console.log( 's.options === "1,2,3,4":', (s.options == '1,2,3,4') );

var z = new Something(9, 10, 11);
console.log( 'z.options === "9,10,11":', (z.options == '9,10,11') );

以上测试产生以下输出:

s.options === "1,2,3,4": true
z.options === "9,10,11": true

答案 28 :(得分:0)

通过使用F(),也就是创建者/工厂函数本身来解决重用临时arguments.callee构造函数的问题,这也很有意思: http://www.dhtmlkitchen.com/?category=/JavaScript/&date=2008/05/11/&entry=Decorator-Factory-Aspect

答案 29 :(得分:-1)

为什么你要把事情变得如此复杂。在 new 之后使用匿名函数,该函数返回带有参数的应用数组的构造函数。

RIGHT JOIN

答案 30 :(得分:-1)

作为一个迟到的答案我虽然我会把它放在这里作为一个更完整的解决方案,使用这里已经概述的许多原理。

Implements.js

为了帮助您入门,这是一个基本用法:

var a = function(){
    this.propa = 'a';
}
var b = function(){
    this.propb = 'b'
}
var c = Function.Implement(a, b); // -> { propa: 'a', propb: 'b' }

答案 31 :(得分:-1)

感谢帖子,我用这种方式:

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

答案 32 :(得分:-2)

This可能是解决这个问题的低效方法,但我认为这对我来说很容易理解。

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

答案 33 :(得分:-2)

function F(a){this.a=a}
Z=F;
f=Function('return new function '+F.name+' ()
{return  Z.apply(this,[1]) } ').call()
console.log(f)

function F(a){this.a=a} 
f= new function(){return F.apply(this,[1])} 
console.log(f) 

答案 34 :(得分:-2)

不应该这样吗?半醒,没仔细阅读。

var Storage = undefined;

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