Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ES6 inheritance: uses `super` to access the properties of the parent class

Javascript's super keyword, when I run the code on Chrome, Babel, TypeScript, I got different results.

My question is which result is correct? And what part of specification defines such behavior?

The following code:

class Point {
  getX() {
    console.log(this.x); // C
  }
}

class ColorPoint extends Point {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(this.x)   // A
    console.log(super.x)  // B
  }
  
  m() {
    this.getX()
  }
}

const cp = new ColorPoint();
cp.m();

The results:

  • Chrome 58.0.3029.110 64bit (V8 5.8.283.38)
  • Babel Repl 6.24.2
  • TypeScript 2.3

links:

  • gist
  • babel
like image 484
justjavac Avatar asked May 24 '17 04:05

justjavac


1 Answers

Short answer:

Chrome is correct. And this is caused by the unbalance between get and set.

OrdinarySet is reciever sensitive, but OrdinaryGet is not.

So super.x = 3 has the same effect of this.x = 3, because the receiver here is this. Evaluatingsuper.x, which never reach this, will always get undefined because A.prototype does not have such field.


More details:

The super.x is a SuperReference. And assignment to SuperReference will call PutValue(V, W), which in turn call super object's internal slot [[Set]] and finally OrdinarySet.

In plain JavaScript, the statement super.x = 3 is basically equivalent to:

OrdinarySet(proto, 'x', 3, this).

Where proto is the super object, internally the [[HomeObject]] of constructor ColorPoint. proto is equivalent to Object.create(Point.prototype), as the ClassDefinitionEvaluation specifies, and it is passed to constructor as [[HomeObject]].


Now let's see how OrdinarySet works. In step 4c and 4d, the spec requires set operation is done on receiver this, not the proto object.

Let existingDescriptor be ? Receiver.[GetOwnProperty].

If existingDescriptor is not undefined, then

If IsAccessorDescriptor(existingDescriptor) is true, return false.

If existingDescriptor.[[Writable]] is false, return false.

Let valueDesc be the PropertyDescriptor{[[Value]]: V}.

Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).

These statements says OrdinarySet(proto, 3, this) means this.x = 3.


On the other hand, OrdinaryGet ignores Receiver . super.x is

OrdinaryGet(proto, 'x', this).

OrdinaryGet does not have Receiver in its clauses at all! So super.x is equivalent to Object.create(Point.prototype).x, which is undefined of course.

As a rule of thumb, if there is discrepancy between transpilers and browsers, browsers, especially Chrome, are usually more loyal to the ECMAScript specification. Transpilers usually trade some edge case correctness for runtime efficiency.

like image 80
Herrington Darkholme Avatar answered Nov 19 '22 11:11

Herrington Darkholme