javascriptclassinheritanceprototype

Why is super() calling the wrong constructor when using Object.setPrototypeOf?


I have the following code:

class Polygon {
  constructor() {
    this.name = "Polygon";
  }
}

class Rectangle {
  constructor() {
    this.name = "Rectangle";
  }
}

class Square extends Polygon {
  constructor() {
    super(); 
  }
}

Object.setPrototypeOf(Square, Rectangle);

const instance = new Square();

console.log(instance.name); // Rectangle

My understanding is:

My Question:

In the code above, after using Object.setPrototypeOf(Square, Rectangle), the Square._proto_ is now pointing to Rectangle and not Polygon for all static properties. And for inheriting all the other methods Square.prototype._proto_ points to Polygon.prototype. So, then I expect super() in Square to invoke Polygon's constructor, since Square extends Polygon. However, it seems like super() is invoking Rectangle's constructor instead. I'm confused now, what all gets changed when Object.setPrototypeOf(Square, Rectangle) is run, seems like there is a gap in my understanding.


Solution

  • What's missing is where the value of super is coming from.

    1. A base class (as in not extending another class) is prototyped on Function.prototype. For example

      Object.getPrototype(Polygon) === Function.prototype
      
    2. A class extending another causes two things to happen:

      1. The extended class object is prototyped on the class being extended and not on Function.prototype. For example

          Object.getPrototypeOf(Square) === Polygon
        
      2. The extended class's prototype property is prototyped on the prototype property of the class extended. For example

         Object.getPrototypOf(Square.prototype) === Polygon.prototype
        
      3. Note the constructor value inherited from an extended class's prototype is not modified and remains set to the extended class object. I.E.

         Square.prototype.constructor === Square 
        

      This has ramifications: the super class of an extended object can't be determined from the constructor property inherited by instances of the the extended class.

      1. To rephrase point 2.1 above: the object on which an extended class object is prototyped is a) initially its super class object and b) effectively used by the super keyword to locate the super class object's contructor method.

    This code shows the effects of changing the object on which a Class Object is prototyped on for experimental purposes to see what happens. It is not something to do in real world programming.

    class Polygon {
      static ClassName = "Polygon";
      constructor() { this.name = "Polygon instance" }
      inheritsFrom() {return "Polygon"}
    }
    
    class Rectangle {
      static ClassName = "Rectangle";
      constructor() { this.name = "Rectangle instance"; }
      inheritsFrom() {return "Rectangle"}
    
    }
    
    class Square extends Polygon {
      constructor() {
        super();
      }
    }
    
    
    console.log("class Polygon is prototyped on Function.prototype: ",
      Object.getPrototypeOf(Polygon) === Function.prototype, " (1) true expected");
    
    console.log(Square.ClassName, " (2) static value 'Polygon' expected");
    const instance = new Square();
    console.log( instance.name, " (3) 'Polygon instance' expected")
    console.log( instance.constructor.name, " (4) 'Square' expected")
    
    // overwrite the object (the super class) on which Square is prototyped:
    Object.setPrototypeOf(Square, Rectangle);
    
    console.log( Square.ClassName, " (5) static value 'Rectangle' expected");
    const instance2 = new Square(); 
    console.log(instance2.name, " (6) 'Rectangle instance' expected");
    console.log( instance2.constructor.name, " (7) 'Square' expected")
    
    // instances of Square still inherit from Polygon.prototype
    
    console.log("instance instanceof Polygon is ", instance instanceof Polygon,
      " (8) true expected");
    console.log("instance instanceof Rectangle is ",
     instance instanceof Rectangle, " (9) false expected)");
    console.log("instance inherits from ", instance.inheritsFrom(), " (10) 'Polygon' expected");
    
    console.log("instance2 is an instanceOf Polygon ", instance2 instanceof Polygon,
      " (11) true expected");
    console.log("instance2.instanceof Rectangle is ",
      instance2 instanceof Rectangle, " (12) false expected");
    console.log("instance2 inherits from ", instance2.inheritsFrom(),
        " (13) 'Polygon' expected");

    Update: usage of __proto__ as an inherited getter to return null or the object on which another has been prototyped on has been marked as "NORMATIVE OPTIONAL, LEGACY" in ECMAScript and hence considered deprecated: use of __proto__ has been removed from test code.

    In all of the above,