Search code examples
javascriptoopinheritanceprototype

JS Inheritance: calling the parent's function from within the child's function


There must be something I don't understand about the JS object model.

From these resources:

I have gathered what I think, or thought, was an accurate mental representation of the object model. Here it is:


All objects have a property, which the docs refer to as [[Prototype]]. [[Prototype]] can be thought of as a reference to the object's parent. More accurately:

The reference to the [parent's] prototype object is copied to the internal [[Prototype]] property of the new instance. (source 1)

You can get access to the [[Prototype]] property of the child with Object.getPrototypeOf(child) The value returned here will be a reference to the parent's prototype (not its internal [[Prototype]] property, but its actual prototype)

obj.prototype is different from the object's internal [[Prototype]] property. It acts like the blueprints used to make instances of this exact object, while its [[Prototype]] property points to the blueprints used to make instances of its parent.

  • Parent.prototype === Object.getPrototypeOf(child); //true

To elaborate:

  • If you add a function to child.prototype the function will be available to child and any of it's children.

  • If you add a function to parent.prototype, which is equivalent to adding a function to Object.getPrototypeOf(child), then the function will be available to parent and all of it's children, which includes child and all of its siblings.

You can use Object.create() to create a new object with whatever [[Protoype]] property you want. So you can use it as a way to implement inheritance. See source 2 for an example.


With this in mind, I wanted to get a working example of my own going. My plan was to create a parent 'class' and then make a child 'class' that inherited from it.

I wanted the child class to implement a method, which overloaded a method from the parent. The caveat is that I want the child's version of the method to call the parent's version of the method and then do some extra stuff.

Here is what I came up with, see below for the issues associated with it:

var Parent = function() {};

Parent.prototype.myF = function() {
  console.log('derp');
};


function Child() {
  Parent.call(this);
};

//make [[prototype]] of Child be a ref to Parent.prototype
Child.prototype = Object.create(Parent.prototype);

//need to explicitly set the constructor
Child.prototype.constructor = Child;

Child.prototype.myF = function() {
  Object.getPrototypeOf(this).myF();
  console.log("did I derp??");
};

childInstance = new Child();
childInstance.myF();

It appears to be the case that when I attempt to overload Parent.myF(), while I am overloading it, I am actually modifying the original function at the same time. This appears to be the case because the logged results are:

'derp'
'did I derp??'
'did I derp??'

presumably the first occurance of 'did I derp??' is coming from a modified version of the parent's function, which I don't mean to do, then the second version is coming from the child's function.

Can anyone elaborate on why this is happening?


Solution

  • Great question, it took a bit of testing and researching to find it out.

    Identifying the strange behaviour

    I changed your code a little bit to find out which function is called when:

    var Parent = function() {};
    
    Parent.prototype.myF = function() {
      console.log('derp');
    };
    
    
    function Child() {
      Parent.call(this);
      this.name = 'Test'; // this is a new test property
    };
    
    //make [[prototype]] of Child be a ref to Parent.prototype
    Child.prototype = Object.create(Parent.prototype);
    
    //need to explicitly set the constructor
    Child.prototype.constructor = Child;
    
    Child.prototype.myF = function() {
      console.log(this); // here I want to find out the context, because you use it in the next line
      Object.getPrototypeOf(this).myF();
      console.log("did I derp??");
    };
    
    childInstance = new Child();
    childInstance.myF();
    

    You can check out the JSFiddle and try it for yourself: http://jsfiddle.net/Lpxq78bs/

    The crucial line in your code is this:

    Child.prototype.myF = function() {
      Object.getPrototypeOf(this).myF(); // this one
      console.log("did I derp??");
    };
    

    After doing a console.log(this); to find out what this refers to, I saw that it changes between the first and the second output of did I derp??.

    I got the following output:

    Object { name: "Test" }
    Object { constructor: Child(), myF: window.onload/Child.prototype.myF() }
    "derp"
    "did I derp??"
    "did I derp??"
    

    Interpreting the Output

    Since I added a 'name' property to the Child constructor, it would only be around if I am looking at an instance of Child, not at its .prototype.

    So the first line of the Output means that the current this context is indeed the childInstance. But the second one is neither the childInstance, nor the Parent.prototype:

    1. Call (myF of childInstance): this refers to the childInstance. Object.getPrototypeOf(this).myF(); then looks for the [[Prototype]] of the childInstance, which is the Child.prototype, not the Parent.prototype. Output: 'did I derp??'

    2. Call (myF of Child.prototype): this refers to the Child.prototype, which is the childInstances [[Prototype]] Property. So the second call of Object.getPrototypeOf(this).myF(); finally returns the Parent.prototype (sort of). Output: 'did I derp??'

    3. Call (myF of Parent.prototype instance created by Object.create): Finally, the myF on the parent is called. Output: 'derp'

    Since your console.log("did I derp??") comes after the myF function call, the output is in reverse order. The following graphic illustrates how the code is traversed:

    enter image description here

    So your assumption about what Object.getPrototypeOf(this).myF(); refers to, was wrong.

    Solution in ES5

    By @LukeP: https://jsfiddle.net/Lpxq78bs/28/

    Alternative Solution in ES6

    To avoid this confusion, and since you are working with a classical inheritance pattern, you could have a look at ES6 Classes. The following would be a rough example of what you are trying to do:

    class Parent {
        constructor() {
          
        }
        
        myF() {
            console.log('derp');
        }
    }
    
    class Child extends Parent {
        constructor() {
            super();
        }
    
        myF() {
            super.myF();
            console.log("did I derp??");
        }
    }
    
    var childInstance = new Child();
    childInstance.myF();
    

    I hope this helps in understanding what happens.