Как я могу вызвать конструктор javascript, используя вызов или применить?

Как я могу обобщить функцию ниже, чтобы принять N аргументов? (Использование вызова или применения?)

Существует ли программный способ применения аргументов к "новому"? Я не хочу, чтобы конструктор обрабатывался как простая функция.

/**
 * This higher level function takes a constructor and arguments
 * and returns a function, which when called will return the 
 * lazily constructed value.
 * 
 * All the arguments, except the first are pased to the constructor.
 * 
 * @param {Function} constructor
 */ 

function conthunktor(Constructor) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        console.log(args);
        if (args.length === 0) {
            return new Constructor();
        }
        if (args.length === 1) {
            return new Constructor(args[0]);
        }
        if (args.length === 2) {
            return new Constructor(args[0], args[1]);
        }
        if (args.length === 3) {
            return new Constructor(args[0], args[1], args[2]);
        }
        throw("too many arguments");    
    }
}

Тест qUnit:

test("conthunktorTest", function() {
    function MyConstructor(arg0, arg1) {
        this.arg0 = arg0;
        this.arg1 = arg1;
    }
    MyConstructor.prototype.toString = function() {
        return this.arg0 + " " + this.arg1;
    }

    var thunk = conthunktor(MyConstructor, "hello", "world");
    var my_object = thunk();
    deepEqual(my_object.toString(), "hello world");
});

Ответ 1

Попробуйте следующее:

function conthunktor(Constructor) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {

         var Temp = function(){}, // temporary constructor
             inst, ret; // other vars

         // Give the Temp constructor the Constructor prototype
         Temp.prototype = Constructor.prototype;

         // Create a new instance
         inst = new Temp;

         // Call the original Constructor with the temp
         // instance as its context (i.e. its 'this' value)
         ret = Constructor.apply(inst, args);

         // If an object has been returned then return it otherwise
         // return the original instance.
         // (consistent with behaviour of the new operator)
         return Object(ret) === ret ? ret : inst;

    }
}

Ответ 2

Вот как вы это делаете:

function applyToConstructor(constructor, argArray) {
    var args = [null].concat(argArray);
    var factoryFunction = constructor.bind.apply(constructor, args);
    return new factoryFunction();
}

var d = applyToConstructor(Date, [2008, 10, 8, 00, 16, 34, 254]);

Вызов немного проще

function callConstructor(constructor) {
    var factoryFunction = constructor.bind.apply(constructor, arguments);
    return new factoryFunction();
}

var d = callConstructor(Date, 2008, 10, 8, 00, 16, 34, 254);

Вы можете использовать любой из них для создания функций factory:

var dateFactory = applyToConstructor.bind(null, Date)
var d = dateFactory([2008, 10, 8, 00, 16, 34, 254]);

или

var dateFactory = callConstructor.bind(null, Date)
var d = dateFactory(2008, 10, 8, 00, 16, 34, 254);

Он будет работать с любым конструктором, а не только с встроенными или конструкторами, которые могут работать как функции (например, Date).

Однако для этого требуется функция. Прокладки, вероятно, будут работать неправильно.

Другой подход, более похожий на стиль некоторых других ответов, заключается в создании функциональной версии встроенного new. Это не будет работать на всех встроенных устройствах (например, Date).

function neu(constructor) {
    // http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.2
    var instance = Object.create(constructor.prototype);
    var result = constructor.apply(instance, Array.prototype.slice.call(arguments, 1));

    // The ECMAScript language types are Undefined, Null, Boolean, String, Number, and Object.
    return (result !== null && typeof result === 'object') ? result : instance;
}

function Person(first, last) {this.first = first;this.last = last};
Person.prototype.hi = function(){console.log(this.first, this.last);};

var p = neu(Person, "Neo", "Anderson");

И теперь, конечно, вы можете сделать .apply или .call или .bind на neu как обычно.

Например:

var personFactory = neu.bind(null, Person);
var d = personFactory("Harry", "Potter");

Я чувствую, что первое решение, которое я даю, лучше, хотя, поскольку оно не зависит от вас, правильно реплицируя семантику встроенного и правильно работает со встроенными.

Ответ 3

Во всех случаях эта функция идентична new. Это, вероятно, будет значительно медленнее, чем ответ 999. Однако, используйте его, только если вам это действительно нужно.

function applyConstructor(ctor, args) {
    var a = [];
    for (var i = 0; i < args.length; i++)
        a[i] = 'args[' + i + ']';
    return eval('new ctor(' + a.join() + ')');
}

ОБНОВЛЕНИЕ: Как только поддержка ES6 будет широко распространена, вы сможете написать следующее:

function applyConstructor(ctor, args) {
    return new ctor(...args);
}

... но вам это не понадобится, потому что стандартная функция библиотеки Reflect.construct() делает именно то, что вы ищете!

Ответ 4

Другой подход, который требует изменения фактического конструктора, который вызывается, но кажется мне более чистым, чем использование eval() или введение новой фиктивной функции в цепочке построения... Сохраняйте свою функцию conthunktor как

function conthunktor(Constructor) {
  // Call the constructor
  return Constructor.apply(null, Array.prototype.slice.call(arguments, 1));
}

И измените вызываемые конструкторы...

function MyConstructor(a, b, c) {
  if(!(this instanceof MyConstructor)) {
    return new MyConstructor(a, b, c);
  }
  this.a = a;
  this.b = b;
  this.c = c;
  // The rest of your constructor...
}

Итак, вы можете попробовать:

var myInstance = conthunktor(MyConstructor, 1, 2, 3);

var sum = myInstance.a + myInstance.b + myInstance.c; // sum is 6

Ответ 5

Использование временного конструктора представляется лучшим решением, если Object.create недоступно.

Если Object.create доступно, использование его - это намного лучший вариант. В Node.js, используя Object.create, получается гораздо более быстрый код. Здесь пример использования Object.create:

function applyToConstructor(ctor, args) {
    var new_obj = Object.create(ctor.prototype);
    var ctor_ret = ctor.apply(new_obj, args);

    // Some constructors return a value; make sure to use it!
    return ctor_ret !== undefined ? ctor_ret: new_obj;
}

(Очевидно, что аргумент args - это список аргументов для применения.)

У меня была часть кода, изначально использовавшая eval для чтения части данных, созданной другим инструментом. (Да, eval является злом.) Это создаст дерево из сотен по тысячам элементов. В основном, механизм JavaScript отвечал за разбор и выполнение кучи выражений new ...(...). Я преобразовал свою систему для анализа структуры JSON, что означает, что мне нужно, чтобы мой код определял, какой конструктор вызывает для каждого типа объекта в дереве. Когда я запускал новый код в своем тестовом наборе, я был удивлен, увидев резкое замедление относительно версии eval.

  • Комплект тестов с версией eval: 1 секунда
  • Набор тестов с версией JSON с использованием временного конструктора: 5 секунд.
  • Комплект тестов с версией JSON, используя Object.create: 1 секунду.

Набор тестов создает несколько деревьев. Я вычислил, что моя функция applytoConstructor вызывалась около 125 000 раз, когда запускается тестовый пакет.

Ответ 6

В ECMAScript 6 вы можете использовать оператор спреда для применения конструктора с новым ключевым словом к массиву аргументов:

var dateFields = [2014, 09, 20, 19, 31, 59, 999];
var date = new Date(...dateFields);
console.log(date);  // Date 2014-10-20T15:01:59.999Z

Ответ 7

В этом случае существует перерегулируемое решение. Для каждого класса, к которому вы хотите позвонить с помощью метода apply или call, вы должны позвонить до конвертацииToAllowApply ('classNameInString'); класс должен быть в том же Scoope o глобальном scoope (я не пытаюсь отправить ns.className, например...)

Имеется код:

function convertToAllowApply(kName){
    var n = '\n', t = '\t';
    var scrit = 
        'var oldKlass = ' + kName + ';' + n +
        kName + '.prototype.__Creates__ = oldKlass;' + n +

        kName + ' = function(){' + n +
            t + 'if(!(this instanceof ' + kName + ')){'+ n +
                t + t + 'obj = new ' + kName + ';'+ n +
                t + t + kName + '.prototype.__Creates__.apply(obj, arguments);'+ n +
                t + t + 'return obj;' + n +
            t + '}' + n +
        '}' + n +
        kName + '.prototype = oldKlass.prototype;';

    var convert = new Function(scrit);

    convert();
}

// USE CASE:

myKlass = function(){
    this.data = Array.prototype.slice.call(arguments,0);
    console.log('this: ', this);
}

myKlass.prototype.prop = 'myName is myKlass';
myKlass.prototype.method = function(){
    console.log(this);
}

convertToAllowApply('myKlass');

var t1 = myKlass.apply(null, [1,2,3]);
console.log('t1 is: ', t1);