How does super work as an object?

class A {
  constructor() {
    this.x = 1;
  }
}

A.prototype.x = 5;

class B extends A {
  constructor() {
    super();
    this.x = 2;
    // Super refers to this when it is assigned
    super.x = 3;
   // When set, super refers to the prototype of the parent class
    console.log(super.x); // 5
    console.log(this.x); // 3
  }
}

let b = new B();

But, why?

When I try to understand these types of cases I like to use babel to see what the code becomes when it is transpiled back to ES5.

When I do that. playground

super.x = 3;
super.x;

becomes roughly:

_set(_getPrototypeOf(B.prototype), "x", 3, this, /* isStrict: */ true);
_get(_getPrototypeOf(B.prototype), "x", this);

Which can be roughly compiled back to 'modern JS' as:

class A {
  constructor() {
    this.x = 1;
  }
}

A.prototype.x = 5;

class B extends A {
  constructor() {
    super();
    this.x = 2;
      
    // Set
    Reflect.set(Reflect.getPrototypeOf(B.prototype), "x", 3, this);
      
    // Get
    let v = Reflect.get(Reflect.getPrototypeOf(B.prototype), "x", this);
    console.log(v); // 5
    console.log(this.x); // 3
  }
}

void (new B());

So now we no longer have to think about super, and only look into the difference between Reflect.set and Reflect.get.

(to be continued...)

...continued.

This example can now be reduced to this:

let thePrototypeObject = { x: 5 };
let theReceiverObject  = { x: 2 };

Reflect.get(thePrototypeObject, 'x', theReceiverObject); // 5
Reflect.set(thePrototypeObject, 'x', 3, theReceiverObject); // true (it worked)

thePrototypeObject.x; // still 5
theReceiverObject.x;  // now 3

Which shows that '[[set]]' can create or update a property on the receiver parameter. But '[[get]]' only uses the receiver when a getter is encountered.

I think its worth pointing out that MDN does a bad job with the role of receiver with respect to set. It's description for the receiver argument is simply:

The value of this provided for the call to target if a setter is encountered.

This ignores the very important fact that when present, if not calling a setter, the receiver becomes the target for the property being set.

const a = { x: 1 }
const b = { x: 2 }
Reflect.set(a, 'x', 3, b) // sets on b, not a
console.log({a, b}) // a: { x: 1 }, b: { x: 3 }}

Especially important in this particular case since that's exactly what's happening with super and this in:

Reflect.set(Reflect.getPrototypeOf(B.prototype), "x", 3, this); // super.x = 3;
1 Like

Agreed, feels important to mention this. Issue raised: Issue with "Reflect.set()": Incomplete details on the consequences of the receiver parameter · Issue #14744 · mdn/content · GitHub

2 Likes