Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

At the July 2014 TC39 meeting, we presented a sketch of a new object instantiation design that was inspired from posting of Claude Pache. Here's the basic idea, as originally described by Claude:

If a constructor C uses super in its code, let’s say:

  class C extends B {
      constructor(...args) {
          /* 1: preliminary code that doesn't contain calls to a super-method */
          // ...

          /* 2: call to a super-constructor */
          super(...whatever)

          /* 3: the rest of the code */
         // ...
      }
  }

the following occurs when C is invoked as constructor, e.g. using new C(...args):

  1. During phase /* 1 */, the this-binding is uninitialised; trying to access it through an explicit this keyword will throw a ReferenceError.
  1. At phase /* 2 */, a call to the super-constructor (or, indeed, any super-method call) will in fact invoke it with the semantics of a constructor, and will initialise the this-binding. It is somewhat as if doing,
     this = new super(...whatever)

except that the default prototype of the created object will be D.prototype rather than super.prototype, where D is the constructor on which the new operator was originally applied (the reference to D being forwarded by the super-call as needed).

  1. During phase /* 3 */, the this-binding is initialised, and any call to a super-method has the normal semantics of method (not constructor).

The main innovation present at the meeting was the introduction of a new syntactic token new* that can be used to distigish a [[Construct]] initiated evaluation of a constructor from a [[Call]] initiated evaluation.

Subsequent to this meaning we extensively discussed, refined and polished the design. In addition to changing new* to new^ there we made three other major changes:

  1. Using new super() rather than super() as the invocation of the superclass constructor via [[Construct]]
  2. Requiring an explicit this= rather than making the first super() call implicitly assign to this.
  3. Allowing anything to be assigned to this from within the this TDZ of a constructor.

###Rationale for change #1 Consider a constructor such as:

class D extends B {
   constructor () {
      If (!new^) super();
      if (Math.random() >0.5) super();
      f(super());
   }
}

Which of the super() expressions will do [[Construct]] invocation and which will do [[Call]] invocation? How does it change if the code is refactored? What happens if a new is put in front of any of the above super() expressions? Does it change the semantics of any, some, or all of them?

We already have enough confusions between "called as a function" and "called as a constructor". As part of this proposal, for the first time, ECMAScript will allow a constructor to dynamically determine which way it was called. In general x() means invoke via [[Call]] and new x() means invoke via [[Construct]]. It would terrible to further confuse things by introducing special situations where super() means [[Construct]] and not [[Call]]. If the ES programmer is thinking I need to let my base constructor create an object, then new super(), is exactly the appropiate idiom and the right way to think about it. Other languages that use super() for this purpose don't have the language level distinction between [[Call]] and [[Construct]].

###Rationale for change #2

As discussed at the July meeting, super(), would magically bind this if it occurs in the this TDZ. We should be adding additional "magic" behavors to ECMAScript: a hidden implicit binding construct. The intent of explicitly setting this is much clearer then super() which might might or might not be intended to set this (or be a [[Call]] or [[Construct]] depending upon what has already happened in the method.

Semantically, alloowing super() to mean either [[Construct]] or [[Call]] makes it impossible to statically make the determination of which is meant. Consider the following example:

  constructor() {
    let randomize = o => Math.random()>=0.5 ? !!o: !o;
    if (randomizer(new^)) return super(); //is this a [[Call]] or [[Construct]]?
  }

Changes #1 and #2 fit naturally together. Where in previous designs we expected programmer to know the magic and say"

  constructor() {
    super();  //looks like a [[Call]] acts as a [[Construct]] and sets this
    this.newProp = ...
  }

we now expect programmers to be explicit about their intent by saying:

   constructor() {
      this = new super();  //always a [[Construct]], explicitly binds ths
      this.newProp = ...
   }

No magic, everything is explicit, and it is possible to statically dermine whether a constructor is intended to perform its own allocation and binding of its this value.

###Rationale for change #3 Once you have #2, #3 naturally follows and as we've shown there are lots of use cases for constructors that don't just do the default ordinary allocation.

Change #3 also reduces the difference (and refactoring expense) between common constructor patterns and constructors that do special allocation steps:

Typically a constructor might look like this:

   constructor(a,b) {
      this = new super();
      this.a = a;
      this.b = b;
   }

But without #3, a constructor that does special allocation might look like:

   constructor(a,b) {
      let newObj = new^.specialAllocationMethod();
      newObj.a = a;
      newObj.b = b;
      return newObj.
   }

With #3 such a constructor can be expressed as:

   constructor(a,b) {
      this = new^.specialAllocationMethod();
      this.a = a;
      this.b = b;
   }

Only the this assignment is differnt from the typical construtor pattern.

@briandipalma

This comment has been minimized.

Copy link

briandipalma commented Sep 12, 2014

We should be adding additional "magic" behavors to ECMAScript: a hidden implicit binding construct.

Is that a typo?

@BrendanEich

This comment has been minimized.

Copy link

BrendanEich commented Sep 17, 2014

Right, "shouldn't" not "should" [be adding magic].

/be

@webbedspace

This comment has been minimized.

Copy link

webbedspace commented Sep 19, 2014

I'm wondering if anyone could briefly explain why the ^ symbol is necessary (apart from the obvious purpose of keeping new^ from colliding with identifiers)? In particular, the fact that it points "upward" implies that it refers to something higher in the class hierarchy, when it seems like it instead always refers to a sub-class "below" or equal to the current constructor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.