【问题标题】:How to construct JavaScript object (using 'apply')?如何构造 JavaScript 对象(使用“应用”)?
【发布时间】:2011-05-12 17:11:42
【问题描述】:

我正在寻找一种基于 (a) 构造函数的名称和 (b) 包含参数的数组来构造任意 JavaScript 对象的方法。我在 stackoverflow 的另一个线程中找到了这个函数(由 Matthew Crumley 编写?):

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

这适用于用 JavaScript 编写的构造函数,但如果我尝试使用构造函数(日期,[...]),它会失败并出现 TypeError。我还不知道是否还有更多本函数无法处理的本机构造函数。那么我的问题是......

  • 最新版本的 JavaScript (ECMAScript 5) 中是否有可以解决我的问题的函数?
  • 如果没有,有什么方法可以检查有问题的构造函数,看看是否可以使用上述函数? (如果不能,我可能不得不使用 eval("new "+cname+"("+arglist+")")。)

/乔恩

【问题讨论】:

标签: javascript constructor


【解决方案1】:

在 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]] 内部方法,使用 bind 函数创建的 F 使用参数列表 ExtraArgs 调用时,将执行以下步骤:

  1. 令 target 为 F 的 [[TargetFunction]] 内部属性的值。
  2. 如果目标没有 [[Construct]] 内部方法,则抛出 TypeError 异常。
  3. 令 boundArgs 为 F 的 [[BoundArgs]] 内部属性的值。
  4. 令 args 为一个新列表,其中包含与列表 boundArgs 相同的值,顺序相同,后跟与列表 ExtraArgs 相同的值,顺序相同。
  5. 返回调用目标的 [[Construct]] 内部方法的结果,提供 args 作为参数。

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

【讨论】:

  • 感谢大家的快速解答!我只有机会尝试 Mike Samuel 的功能。当我执行 d = applyCtor(Date, [2000, ...]) 时,我得到一个声称是 Date 的对象,它具有例如一个 getYear 函数,但是当我尝试 d.getYear() 时,Firefox 说“在不兼容的对象上调用了 Date.prototype.getYear”。在 Safari 中,我只是收到“类型错误”。
  • 我现在已经尝试了两种 T.J. Crowder 的 applyConstruct 函数,但是当我进行 Date 测试时,我得到的结果与 Mike 的函数完全相同(参见上面的我之前的评论)。 :-(
  • 我想我在一些我再也找不到的旧代码中用黑客三角形修复了这个问题。基本思想是 switch (args.length) { case 0: return new ctor; case 1: return new ctor(args[0]); case 2: return new ctor(args[0], args[1]); ... } 最多 12 个,默认值如上所述,它使用我知道的构造函数处理所有主机对象。
  • @Mike Samuel:一个小修正:Date 和其他此类对象是内置对象宿主对象是那些实现定义的对象,例如 DOM 对象。
  • @Casablanca:我在上面的代码中所说的“内在”是指包含内置对象。 Intrinsics 包括内置对象和其他完全由语言定义指定的构造,如原始值类型。
【解决方案2】:

我最终可能会得到这个简化版的 Mike 的黑客三角:

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

我没有在这里使用“应用”,但我不会错过它。 ;-) 有没有 cmets?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多