Prototype chaining, calling parent methods behave as if parent constructor never ran

505 views Asked by At

Ok so we're trying to get prototype inheritance working the way we want it to, I've read a few examples, but one requirement we wanted was that we could call methods on the parent class easily. And we want to follow the module pattern + jQuery boilerplate style where we have defaults, a non-empty constructor function and prototype functions

;(function($, window, undefined){

    "use_strict";

    var defaultsHuman = {

        id: 1,

        age: 0
    };

    function Human( options ){

        this.options = $.extend(defaultsHuman, options || {});

        this.age = this.options.age;
        this.gender = 'male';

        //save originals for ref
        this._defaults = defaultsHuman;

    };
    Human.prototype = {

        _className: 'Human',

        init: function(){

            console.log('My class is ' + this._className + ' my gender is ' + this.gender + ' and my age is ' + this.age);

        }

    };

    //Right now Human's function prototype's constructor is Object(), but IE8 uses constructor.prototype
    //well now it's Object which is native so it's undefined?, anyways we lose the original reference to the constructor from the instance
    //so lets reset it to the constructor - constructor is now enumerable!

    Human.prototype.constructor = Human; //note this is cyclical!

    //END fn Human


    var defaultsChild = {

        name: ''

    };

    //we want to create a new constructor B that has properties, its constructor prototype is an instance of Human
    function Child( options ){

        //merge the parent defaults with my defaults, then extend dynamic options on top
        this.options = $.extend(this.constructor.prototype._defaults, defaultsChild, options || {});

        this.name = options.name;

        //A.call(this);

    };
    //new Human() calls Human's constructor and returns an object with prototype set to Human.prototype
    Child.prototype = new Human();

    $.extend(Child.prototype, {

        school: 'St. Peter\'s',

        init: function(){

            //create reference to super class
            this._super = this.constructor.prototype;

            //this._super.init.call(this);
            this._super.init();

            console.log('My name is ' + this.name + ' and my school is ' + this.school); 
        }

    });
        Child.prototype.constructor = Human;
    //END Child


    //export modules - old method before define
    window.Human = Human;
    window.Child = Child;

})(jQuery, window, undefined);



//some other closure somewhere where it is required in
;(function(window, undefined, Human, Child){

    "use_strict";

    var me = new Child({
        name: 'Clarence',
        age: 7
    }).init();


})(window, undefined, Human, Child);

What is confusing me is that in Human's init function the this refers to a Human instance, but in a state as if the Human constructor never ran, so gender which is statically set to male isn't even there.

My class is Human my gender is undefined and my age is undefined 
My name is Clarence and my school is St. Peter's 

I can easily fix this by calling this._super.init.call(this); instead, which I'll probably just do, but I am still curious.

I explicitly set the function prototype of Child to a complete Human object after the constructor had run: Child.prototype = new Human(); when I inspect the final instance of child me the prototype is Human where the constructor had run (as expected), but inside the Human init the this variable is such that the Human constructor had never run.

When I reference my super: this._super = this.constructor.prototype; is this not a reference to the prototype declared here Child.prototype = new Human();? And when I call this.super.init() is it not running in the context of what was returned by new Human()?


Also please note I am avoiding proto for compatibility with IE8

1

There are 1 answers

3
HMR On BEST ANSWER

Not sure if I understand this correctly but you can do the following:

function Human( options ){

    this.options = $.extend(defaultsHuman, options || {});

    this.age = this.options.age;
    this.gender = 'male';

    console.log("whaat",this.age);

    //save originals for ref
    this._defaults = defaultsHuman;

};
function Child( options ){
    Human.call(this, options);
};
Child.prototype = Object.create(Human.prototype);
function Human( options ){

    this.options = $.extend(defaultsHuman, options || {});

    this.age = this.options.age;
    this.gender = 'male';

    //save originals for ref
    this._defaults = defaultsHuman;

};

If you want to support IE 8 and below or older browsers that don't have Object.create you can use the polyfil or check out this answer that has a helper function for inheritance with constructor functions.

If I call Human.prototype.init there is no instance value for this. This will point to Human.prototype instead.

  this.constructor === Human;
  this._super === this.constructor.prototype === Human.prototype;
  this._super.init === Human.prototype.init;

If you want to use a default Human value on the Child.prototype than you should know that Hukman is shared for all instances of Child. If you then want to call the init on that you can do so like this:

    Child.prototype = Object.create(Human.prototype);
    Child.prototype.humanInstance = new Human();

    //... in the Child constructor body:
    this.humanInstance.init();