Использование .apply () с оператором 'new'. Это возможно? - PullRequest
443 голосов
/ 22 октября 2009

В 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. Тем не менее, люди предложили ряд действительно интересных решений этой проблемы.

Мое предпочтительное решение было это от Мэтью Крамли (я изменил его, чтобы передать свойство arguments):

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

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

Ответы [ 35 ]

4 голосов
/ 22 октября 2009

, если вы заинтересованы в решении на основе eval

function createSomething() {
    var q = [];
    for(var i = 0; i < arguments.length; i++)
        q.push("arguments[" + i + "]");
    return eval("new Something(" + q.join(",") + ")");
}
3 голосов
/ 20 мая 2011

Смотрите также, как это делает 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(){});
3 голосов
/ 03 февраля 2017

Это работает!

var cls = Array; //eval('Array'); dynamically
var data = [2];
new cls(...data);
2 голосов
/ 01 сентября 2017

Раствор без ES6 или полифилов:

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: "X", arg2: "Y", arg3: "Z"}


... или "более коротким" способом:

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);
    }};
}
2 голосов
/ 13 марта 2015

Этот подход конструктора работает как с ключевым словом 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, но вы всегда можете это сделать, если вы поддерживаете старые браузеры. См. Таблицу поддержки для MDN здесь .

Вот JSBin, чтобы увидеть его в действии с консольным выводом .

1 голос
/ 06 сентября 2011

Решения Мэтью Крамли в CoffeeScript:

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
)()
1 голос
/ 04 апреля 2015

модифицированный ответ @Matthew. Здесь я могу передать любое количество параметров, чтобы функционировать как обычно (не массив). Также «Нечто» не жестко закодировано в:

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 );
1 голос
/ 23 октября 2009

Вы не можете вызвать конструктор с переменным числом аргументов, как вы хотите с оператором 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]);
1 голос
/ 16 августа 2013
function createSomething() {
    var args = Array.prototype.concat.apply([null], arguments);
    return new (Function.prototype.bind.apply(Something, args));
}

Если ваш целевой браузер не поддерживает ECMAScript 5 Function.prototype.bind, код не будет работать. Хотя это не очень вероятно, см. таблицу совместимости .

1 голос
/ 10 июля 2015

Этот однострочник должен сделать это:

new (Function.prototype.bind.apply(Something, [null].concat(arguments)));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...