Как построить объект JavaScript (используя «применить»)? - PullRequest
4 голосов
/ 19 ноября 2010

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

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 («new» + cname + »(« + arglist + »)»).)

/ Jon

Ответы [ 2 ]

4 голосов
/ 19 ноября 2010

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

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

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

Я не использую здесь «применять», но я не пропускаю это.;-) Есть комментарии?

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