Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
314 views
in Technique[技术] by (71.8m points)

constructor - How to construct JavaScript object (using 'apply')?

I'm looking for a way to construct arbitrary JavaScript objects based on (a) the name of the constructor, and (b) an array containing the arguments. I found this function (by Matthew Crumley ?) in an other thread on stackoverflow:

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

This works well with constructors written in JavaScript, but it fails with a TypeError if I try construct(Date, [...]). I don't know yet if there are more native constructors that this function can't handle. My questions are then ...

  • Are there functions in more recent versions of JavaScript (ECMAScript 5) that will solve my problem?
  • If not, is there some way I can check the constructor in question to see if the above function can be used? (If it cannot, I may have to use eval("new "+cname+"("+arglist+")").)

/Jon

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

In ES5, you can do it via bind.

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

which works because bind still uses the [[Construct]] abstract operator when the bound function appears to the right of new per http://es5.github.com/#x15.3.4.5.2 which says

15.3.4.5.2 [[Construct]]

When the [[Construct]] internal method of a function object, F that was created using the bind function is called with a list of arguments ExtraArgs, the following steps are taken:

  1. Let target be the value of F’s [[TargetFunction]] internal property.
  2. If target has no [[Construct]] internal method, a TypeError exception is thrown.
  3. Let boundArgs be the value of F’s [[BoundArgs]] internal property.
  4. Let args be a new list containing the same values as the list boundArgs in the same order followed by the same values as the list ExtraArgs in the same order.
  5. Return the result of calling the [[Construct]] internal method of target providing args as the arguments.

but most implementations of Function.prototype.bind that attempt to back-port the language feature onto ES3 implementations do not correctly handle bound functions used as a constructor, so if you're not sure your code is running on a real ES5 implementation then you have to fall back to the triangle of hackery:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...