Use of .apply() with 'new' operator. Is th

2018-12-31 03:11发布

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?

What I want to do is something like this (but the code below does not work):

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

The Answer

From the responses here, it became clear that there's no built-in way to call .apply() with the new operator. However, people suggested a number of really interesting solutions to the problem.

My preferred solution was this one from Matthew Crumley (I've modified it to pass the arguments property):

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

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

30条回答
泪湿衣
2楼-- · 2018-12-31 04:11

Matthew Crumley's solutions in CoffeeScript:

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

or

createSomething = (->
    F = (args) -> Something.apply this, args
    F.prototype = Something.prototype
    return -> new Something arguments
)()
查看更多
长期被迫恋爱
3楼-- · 2018-12-31 04:12

See also how CoffeeScript does it.

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

becomes:

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(){});
查看更多
人气声优
4楼-- · 2018-12-31 04:13

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.

查看更多
临风纵饮
5楼-- · 2018-12-31 04:13

You can't call a constructor with a variable number of arguments like you want with the new operator.

What you can do is change the constructor slightly. Instead of:

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

Do this instead:

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

Or if you must use an array:

function Something(args) {
    var x = args[0];
    var y = args[1];
}
var obj = new Something([0, 0]);
查看更多
回忆,回不去的记忆
6楼-- · 2018-12-31 04:13

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

查看更多
梦醉为红颜
7楼-- · 2018-12-31 04:14

This constructor approach works both with and without the new keyword:

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

It assumes support for Object.create but you could always polyfill that if you're supporting older browsers. See the support table on MDN here.

Here's a JSBin to see it in action with console output.

查看更多
登录 后发表回答