skip to Main Content

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:

  • Instance__proto__: Points to Square.prototype to inherit instance methods.
  • Subclass.__proto__: Points to Rectangle to inherit static methods and properties.
  • Square.prototype.__proto__: Points to Polygon.prototype to inherit the parent class’s instance methods.

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.

2

Answers


  1. I’ll leave off most details concerning classes, prototypes, and what not – you can read about them at MDN’s object prototypes and classes pages.

    However: Generally, in JS a class is not pre-backed like it is in other languages. JS allows to alter a class’ definition at runtime, so the call to super() can point to different constructors throughout script execution if you want that. In that regards it is more like a class in Common Lisp and not like one in Java.

    When you write

    class Square extends Polygon {
     // ... elided ...
    }
    

    it is conceptually the same as if you write

    function Square() {}
    Square.prototype = Polygon.prototype;
    

    So when you write

    Object.setPrototypeOf(Square, Rectangle);
    

    it can (again conceptually) be translated into

    Square.prototype = Rectangle.prototype;
    

    and that means that at the time

    super();
    

    is executed, it points to Rectangle.prototype.constructor() in the context of a Square instance. Hence you end up with this.name being "Rectangle".

    Login or Signup to reply.
  2. 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

      Polygon.__proto__ === Function.prototype
      
    2. A class extending another causes two things to happen:

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

          Square.__proto__ === Polygon
        
      2. The extended class’s prototype property is prototyped on the prototype property of the class extended. For example

         Square.prototype.__proto__ === 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 above: the value of the__proto__ property of an extended class is its super class object.

    This code shows the effects of changing the __proto__ property of an extended class as an experiment to see what happens.

    class Polygon {
      static className = "Polygon";
      constructor() { this.name = "Polygon instance" }
    }
    
    class Rectangle {
      static className = "Rectangle";
      constructor() { this.name = "Rectangle instance"; }
    
    }
    
    class Square extends Polygon {
      constructor() {
        super();
      }
    }
    
    
    console.log( Polygon.__proto__ === Function.prototype, " - true expected");
    
    console.log( Square.className, " - Polygon expected");
    const instance = new Square();
    console.log( instance.name, " - Polygon instance expected")
    console.log( instance.constructor.name, " Square expected")
    
    // overwrite `__proto__` (the super class) of Square:
    Object.setPrototypeOf(Square, Rectangle);
    
    console.log( Square.className, " - Rectangle expected");
    const instance2 = new Square(); 
    console.log(instance2.name, " - Rectangle instance expected");
    console.log( instance2.constructor.name, " Square expected")

    In all of the above, the static className property is inherited from Square.__proto__ , name is set by the constructor called as super() and constructor.name is inherited from the first object in the inheritance chain of an instance.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search