2010-11-19 3 views
4

Я ищу способ создания произвольных объектов JavaScript на основе (a) имени конструктора и (b) массива, содержащего аргументы. Я нашел эту функцию (Мэтью Крамли?) В другом потоке на StackOverflow:Как построить объект JavaScript (используя «применить»)?

function construct(constructor, args) { 
    function F() { return constructor.apply(this, args); } 
    F.prototype = constructor.prototype; 
    return new F(); 
} 

Это хорошо работает с конструкторами, написанных на JavaScript, но он терпит неудачу с TypeError, если я пытаюсь построить (Date, [... ]). Я еще не знаю, есть ли дополнительные конструкторы, которые эта функция не может обрабатывать. Мои вопросы тогда ...

  • Есть ли функции в более поздних версиях JavaScript (ECMAScript 5), которые позволят решить мою проблему?
  • Если нет, можно ли каким-либо образом проверить соответствующий конструктор, чтобы увидеть, можно ли использовать вышеуказанную функцию? (Если он не может, я, возможно, придется использовать Eval ("новый "+ CNAME +"(" + список аргументов + ")").)

/Jon

+3

По крайней мере, связанный, если не дубликат: http: // stackoverflow.com/questions/3871731/dynamic-object-construction-in-javascript В любом случае, мой ответ там может помочь. –

+0

@TJC: отличный ответ там (+1). @user: ответ TJ в этом вопросе должен помочь вам начать работу. Поскольку вы специально упоминали «Date», вы можете захотеть проверить 5-ю шайбу ECMA Криса Коваля, которая переопределяет конструктор 'Date', должным образом учитывая переменное количество аргументов, которое оно может принять. https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js#L501 –

ответ

4

В ES5, вы можете сделать это с помощью bind.

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

, который работает, потому что bind до сих пор использует [[Construct]] абстрактный оператор, когда граница функции появляется справа от new на http://es5.github.com/#x15.3.4.5.2, который говорит

15.3.4.5.2 [[Construct] ]

Когда [[Construct]] внутренний метод функционального объекта, F, который был создан с помощью функции bind вызывается со списком аргументов ExtraArgs, предпринимаются следующие шаги:

  1. Пусть цель будет значением внутреннего свойства F [[TargetFunction]].
  2. Если цель не имеет внутреннего метода [[Construct]], генерируется исключение TypeError.
  3. Пусть boundArgs является значением внутреннего свойства F [[BoundArgs]].
  4. Пусть args - новый список, содержащий те же значения, что и список boundArgs в том же порядке, за которым следуют те же значения, что и список ExtraArgs в том же порядке.
  5. Возвращает результат вызова внутреннего метода target [[Construct]], предоставляющего аргументы в качестве аргументов.

но большинство реализаций Function.prototype.bind, которые пытаются поддержать-порт функции языка на реализации ES3 не правильно обрабатывать связанные функции, используемые в качестве конструктора, так что если вы не уверены, что ваш код работает на реальной реализация ES5, то вы должны падать обратно в треугольник повозки, запряженные волы:

function applyCtor(ctor, args) { 
    // Triangle of hackery which handles host object constructors and intrinsics. 
    // Warning: The goggles! They do nothing! 
    switch (args.length) { 
    case 0: return new ctor; 
    case 1: return new ctor(args[0]); 
    case 2: return new ctor(args[0], args[1]); 
    case 3: return new ctor(args[0], args[1], args[2]); 
    case 4: return new ctor(args[0], args[1], args[2], args[3]); 
    case 5: return new ctor(args[0], args[1], args[2], args[3], args[4]); 
    case 6: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5]); 
    case 7: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); 
    case 8: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); 
    case 9: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); 
    case 10: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); 
    case 11: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); 
    case 12: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); 
    } 
    // End triangle of hackery 

    // Create a throwaway subclass of ctor whose constructor does nothing. 
    function TemporarySubclass() {} 
    TemporarySubclass.prototype = ctor.prototype; 
    var instance = new TemporarySubclass(); 
    instance.constructor = ctor; // Patch constructor property 
    // Run the constructor. This assumes that [[Call]] internal method is the same as 
    // [[Construct]]. It might work with some builtins/host objects where "new` would not. 
    var returnValue = ctor.apply(instance, args); 
    // If the constructor returned a non-primitive value, return it instead. 
    switch (typeof returnValue) { 
    case 'object': 
     // If ctor is Array, it reaches here so we don't use broken Array subclass. 
     // Ditto for Date. 
     if (returnValue) { return returnValue; } 
     break; 
    case 'function': 
     return returnValue; 
    } 
    // Return the constructed instance. 
    return instance; 
} 
+0

Спасибо всем вам за очень быстрые ответы! У меня был только шанс попробовать функцию Майка Самуэля. Когда я делаю d = applyCtor (Date, [2000, ...]), я получаю объект, который утверждает, что является Датой, и имеет, например, функция getYear, но когда я пытаюсь d.getYear(), Firefox говорит: «Date.prototype.getYear вызывается несовместимый объект». В Safari я просто получаю «Ошибка типа». –

+0

Я сейчас попробовал оба T.J. Crowder's applyConstruct, но когда я выполняю свои тесты Date, я получаю точно такие же результаты, как и с функцией Майка (см. Мой предыдущий комментарий выше). :-( –

+0

Я думаю, что исправил это с помощью треугольника хакерства в каком-то более старом коде, который больше не могу найти. Основная идея: 'switch (args.length) {case 0: return new ctor; case 1: return new ctor (args [0]), case 2: вернуть новый ctor (args [0], args [1]); ...} ' до 12 с по умолчанию, как указано выше, который обрабатывает все объекты хоста конструкторами, которые Я знаю. –

1

Я могу в конечный итоге с этой упрощенной версией Майка треугольник из-повозки, запряженные волов:

function applyCtor2(ctor, args) { 
    switch (args.length) { 
    case 0: return new ctor(); 
    case 1: return new ctor(args[0]); 
    case 2: return new ctor(args[0], args[1]); 
    // add more cases if you like 
    } 
    var jsStr = "new ctor(args[0]"; 
    for (var i=1; i<ar.length; i++) jsStr += ",args[" + i + "]"; 
    jsStr += ")"; 
    return eval(jsStr); 
} 

Я не использую 'apply' здесь, но я не пропущу его. ;-) Любые комментарии?

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