Динамическое построение объекта в javascript?

Когда я хочу вызвать функцию в javascript с аргументами, предоставленными из других источников, я могу использовать метод apply для функции вроде:

array = ["arg1", 5, "arg3"] 
...
someFunc.apply(null, array);

но что, если мне нужно вызвать конструктор аналогичным образом? Это не работает:

array = ["arg1", 5, "arg3"] 
...
someConstructor.apply({}, array);

по крайней мере не так, как я пытаюсь:

template = ['string1', string2, 'etc'];
var resultTpl = Ext.XTemplate.apply({}, template);

это не работает wither:

Ext.XTemplate.prototype.constructor.apply({}, template);

Любой способ заставить эту работу работать? (В этом конкретном случае я обнаружил, что new Ext.XTemplate(template) будет работать, но меня интересует общий случай)

похожий вопрос, но специфичный для встроенных типов и без ответа, который я могу использовать: Создание объекта JavaScript с помощью вызова prototype.constructor.apply

Спасибо.

Изменить:

Время прошло, и ES6 и транспилеры теперь вещь. В ES6 тривиально делать то, что я хотел: new someConstructor(...array). Babel превратит это в ES5 new (Function.prototype.bind.apply(someConstructor, [null].concat(array)))();, который объясняется в Как построить объект JavaScript (используя "apply" )?.

Ответы

Ответ 1

Нет простого и простого способа сделать это с помощью функции-конструктора. Это связано с тем, что при использовании ключевого слова new для вызова функции-конструктора используются особые вещи, поэтому, если вы этого не сделаете, вы должны подражать всем этим особым вещам. Это:

  • Создание экземпляра нового объекта (вы это делаете).
  • Настройка объекта внутреннего прототипа объекта-конструктора prototype.
  • Установка свойства объекта constructor.
  • Вызов функции-конструктора с этим экземпляром объекта как значением this (вы это делаете).
  • Обработка специального возвращаемого значения из функции конструктора.

Я думаю об этом, но стоит дважды проверить в спецификацию.

Итак, если вы можете избежать этого и просто использовать конструктор напрямую, я бы это сделал.:-) Если вы не можете, вы все равно можете это сделать, это просто неудобно и требует обходных решений. (См. Также этот связанный ответ здесь, в StackOverflow, хотя я здесь охватываю всю землю [и затем некоторые].)

Ваша самая большая проблема - № 2 выше: настройка внутреннего прототипа объекта. Долгое время не было стандартного способа сделать это. Некоторые браузеры поддерживали свойство __proto__, которое это делало, поэтому вы можете использовать это, если оно есть. Хорошей новостью является то, что ECMAScript 5 предлагает способ сделать это явно: Object.create. Таким образом, у таких современных браузеров, как Chrome, это будет. Но если вы имеете дело с браузером, который не имеет ни Object.create, ни __proto__, он становится немного уродливым:

1) Определите пользовательскую конструкторскую функцию.

2) Задайте свойству prototype свойству prototype функции реального конструктора

3) Используйте его для создания экземпляра пустого объекта.

Для этого используется прототип. Затем вы продолжаете:

4) Замените свойство constructor в этом экземпляре реальной конструкторской функцией.

5) Вызовите действительную конструкторскую функцию через apply.

6) Если возвращаемое значение функции реального конструктора является объектом, используйте его вместо созданного вами; в противном случае используйте тот, который вы создали.

Что-то вроде этого (живой пример):

function applyConstruct(ctor, params) {
    var obj, newobj;

    // Use a fake constructor function with the target constructor's
    // `prototype` property to create the object with the right prototype
    function fakeCtor() {
    }
    fakeCtor.prototype = ctor.prototype;
    obj = new fakeCtor();

    // Set the object `constructor`
    obj.constructor = ctor;

    // Call the constructor function
    newobj = ctor.apply(obj, params);

    // Use the returned object if there is one.
    // Note that we handle the funky edge case of the `Function` constructor,
    // thanks to Mike comment below. Double-checked the spec, that should be
    // the lot.
    if (newobj !== null
        && (typeof newobj === "object" || typeof newobj === "function")
       ) {
        obj = newobj;
    }

    // Done
    return obj;
}

Вы можете сделать еще один шаг и использовать только фальшивый конструктор, если хотите, посмотреть, поддерживаются ли Object.create или __proto__, как это (живой пример):

function applyConstruct(ctor, params) {
    var obj, newobj;

    // Create the object with the desired prototype
    if (typeof Object.create === "function") {
        // ECMAScript 5 
        obj = Object.create(ctor.prototype);
    }
    else if ({}.__proto__) {
        // Non-standard __proto__, supported by some browsers
        obj = {};
        obj.__proto__ = ctor.prototype;
        if (obj.__proto__ !== ctor.prototype) {
            // Setting it didn't work
            obj = makeObjectWithFakeCtor();
        }
    }
    else {
        // Fallback
        obj = makeObjectWithFakeCtor();
    }

    // Set the object constructor
    obj.constructor = ctor;

    // Apply the constructor function
    newobj = ctor.apply(obj, params);

    // If a constructor function returns an object, that
    // becomes the return value of `new`, so we handle
    // that here.
    if (typeof newobj === "object") {
        obj = newobj;
    }

    // Done!
    return obj;

    // Subroutine for building objects with specific prototypes
    function makeObjectWithFakeCtor() {
        function fakeCtor() {
        }
        fakeCtor.prototype = ctor.prototype;
        return new fakeCtor();
    }
}

В Chrome 6 в приведенном выше примере используется Object.create; на Firefox 3.6 и Opera, он использует __proto__. В IE8 он использует фальшивую конструкторскую функцию.

Вышеприведенная ситуация довольно ошеломляющая, но в основном это касается проблем, о которых я знаю в этой области.

Ответ 2

От developer.mozilla: Связанные функции автоматически подходят для использования с новым оператором для создания новых экземпляров, созданных целевой функцией. Когда связанная функция используется для построения значения, при условии, что это игнорируется. Однако предоставленные аргументы по-прежнему добавляются к вызову конструктора.

Тем не менее, нам все равно нужно использовать apply для получения аргументов из массива и в вызове bind. Кроме того, мы должны также reset связывать функцию как этот аргумент функции apply. Это дает нам очень лаконичный однострочный лайнер, который делает именно то, что необходимо.

function constructorApply(ctor, args){
    return new (ctor.bind.apply(ctor, [null].concat(args)))();
};

Ответ 3

Я думаю, что другим способом достижения этого может быть расширение класса Ext Template, чтобы конструктор нового объекта взял ваш массив и делал то, что вы хотите сделать. Таким образом, для вас будет создан все строительный материал, и вы можете просто вызвать конструктор вашего суперкласса с вашими аргументами.

Ответ 4

Поскольку исходный вопрос довольно старый, здесь более простые методы практически в любом современном браузере (начиная с записи, 2018, я считаю Chrome, FF, IE11, Edge...).

var template = ['string1', string2, 'etc'];
var resultTpl = Object.create(Ext.XTemplate.prototype);
Ext.XTemplate.apply(resultTpl, template);

Эти две строки также объясняют, как работает new оператор.