Use of .apply() with 'new' operator. Is this possible?

后端 未结 30 2686
Happy的楠姐
Happy的楠姐 2020-11-22 00:39

In JavaScript, I want to create an object instance (via the new operator), but pass an arbitrary number of arguments to the constructor. Is this possible?

相关标签:
30条回答
  • 2020-11-22 00:54

    If your environment supports ECMA Script 2015's spread operator (...), you can simply use it like this

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

    Note: Now that the ECMA Script 2015's specifications are published and most JavaScript engines are actively implementing it, this would be the preferred way of doing this.

    You can check the Spread operator's support in few of the major environments, here.

    0 讨论(0)
  • 2020-11-22 00:55

    In ES6, Reflect.construct() is quite convenient:

    Reflect.construct(F, args)
    
    0 讨论(0)
  • 2020-11-22 00:55

    This works!

    var cls = Array; //eval('Array'); dynamically
    var data = [2];
    new cls(...data);
    
    0 讨论(0)
  • 2020-11-22 00:55

    modified @Matthew answer. Here I can pass any number of parameters to function as usual (not array). Also 'Something' is not hardcoded into:

    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 );
    
    0 讨论(0)
  • 2020-11-22 00:56

    It's also intresting to see how the issue of reusing the temporary F() constructor, was addressed by using arguments.callee, aka the creator/factory function itself: http://www.dhtmlkitchen.com/?category=/JavaScript/&date=2008/05/11/&entry=Decorator-Factory-Aspect

    0 讨论(0)
  • 2020-11-22 00:57

    With ECMAScript5's Function.prototype.bind things get pretty clean:

    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
    }
    

    It can be used as follows:

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

    or even directly:

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

    This and the eval-based solution are the only ones that always work, even with special constructors like Date:

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

    edit

    A bit of explanation: We need to run new on a function that takes a limited number of arguments. The bind method allows us to do it like so:

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

    The anything parameter doesn't matter much, since the new keyword resets f's context. However, it is required for syntactical reasons. Now, for the bind call: We need to pass a variable number of arguments, so this does the trick:

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

    Let's wrap that in a function. Cls is passed as argument 0, so it's gonna be our anything.

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

    Actually, the temporary f variable is not needed at all:

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

    Finally, we should make sure that bind is really what we need. (Cls.bind may have been overwritten). So replace it by Function.prototype.bind, and we get the final result as above.

    0 讨论(0)
提交回复
热议问题