2010-10-06 3 views
15

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

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) будет работать, но я заинтересован в общем случае)

подобный вопрос, но специфичны для встроенных типов и без ответа, я могу использовать: Instantiating a JavaScript object by calling prototype.constructor.apply

Спасибо ,

Edit:

Время прошло и ES6 и transpilers теперь вещь. В ES6 тривиально делать то, что я хотел: new someConstructor(...array). Babel превратит это в ES5 new (Function.prototype.bind.apply(someConstructor, [null].concat(array)))();, что объясняется в How to construct JavaScript object (using 'apply')?.

ответ

32

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

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

Я думаю именно об этом, но стоит перепроверять в the spec.

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

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

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

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

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

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

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

5) Позвоните в реальную конструкторскую функцию через apply.

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

Что-то вроде этого (live example):

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

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

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

+0

Это было немного более активное участие, чем я ожидал. Спасибо, хотя, за отличное объяснение! – einarmagnus

+0

@ormuriauga: :-) Рад, что помогло. Объяснение заставляет его казаться немного более сложным, чем есть (как объясняют объяснения). Как вы можете видеть из первого фрагмента кода, это может быть довольно короткий код. –

+0

даже однострочники часто очень привлекательны;) более тонкие детали объектной модели javascript по-прежнему уклоняются от меня, но сейчас я купил «хорошие детали» и скоро прочитаю.Есть ли веская причина использовать последний фрагмент кода, когда работает более короткая и менее сложная? Смогу ли я получить какую-либо скорость или совместимость или что-то еще, используя новые функции? – einarmagnus

0

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

+0

На самом деле, это так - в его вопросе, он сказал * "(В этом конкретном случае я обнаружил, что новый Ext.XTemplate (шаблон) будет работать, но я меня интересует общий случай) »* –

+0

Я думал, что он имел в виду то, что я описал. Извините, если я неправильно понял вопрос. – philgiese

2

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

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

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

Также из соответствующего раздела в связанном источнике: «Внимание: в этом разделе демонстрируются возможности JavaScript и документы с некоторыми крайними случаями метода bind(). Методы, показанные ниже, не являются лучшим способом сделать что-то и, вероятно, не должны использоваться в любой производственной среды ». – drkstr1

+0

Если вы хотите сделать этот параметр args агностиком между фактическим массивом аргументов или объектом * Аргументы *, вы можете сделать что-то вроде ...... '// ... function constructorApply (ctor, args) { return new (ctor.bind.apply (ctor, [null] .concat ([]. Slice.call (args))))(); }; ' – herringtown

Смежные вопросы