динамическая конструкция объекта в JavaScript? - PullRequest
17 голосов
/ 06 октября 2010

Когда я хочу вызвать функцию в 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);

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

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»)? .

Ответы [ 4 ]

35 голосов
/ 06 октября 2010

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

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

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

Так что, если вы можете избежать этого и просто использовать функцию конструктора напрямую, я бы это сделал. :-) Если вы не можете, тем не менее, вы можете сделать это, это просто неловко и требует обходных путей. (См. Также этот связанный ответ здесь, в 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's `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's 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;
}

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

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's 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 он использует функцию фальшивого конструктора.

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

4 голосов
/ 30 января 2015

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

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

function constructorApply(ctor, args){
    return new (ctor.bind.apply(ctor, [null].concat(args)))();
};
1 голос
/ 04 декабря 2018

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

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

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

0 голосов
/ 06 октября 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...