JavaScript: Prototypal Object Inheritance

Overview and Key Facts

  • According to Douglas Crockford and  Eric Elliot Prototypal Inheritance should be preferred over Classical  Inheritance
  • Objects in Javascript inherit their Properties/Methods from what is called a prototype instead of a class definition
  • Prototypes are standard objects themselves and builds up a Prototype Chain      
  • The Prototype Chain top level object is Object.prototype and delegates methods like toString() to each JavaScriot Object with a working Prototype Chain
  • Same prototype means same object instance
  • Every JavaScript Object should get its behavior from functions in its prototype
  • ECMAScript 5 introduced a new method: Object.create() which simplifies creating a Prototype Chain without Douglas Crockfords “WorkAround” simulating Object.create()

 Inherit from a Object Literal using Object.create

  • Object.create(Mammal, …  uses the Mammel Object  created from am  Object Literal  as its Prototype
  • MammelObj inherits Properties type and objectName from Mammel
  • MammelObj inherits Function getDetails from Mammel
  • MammelObj adds _objectName Data Descriptor
  • MammelObj adds an Accessor Descriptor objectName which works on _objectName property
JavaScript Code:

var Mammal = {
    type: 'Mammal',
    objectName : 'Mammal BASE Object',
    getDetails: function () {
        return("Type: " + this.type + " - ObjectName:  "  + this.objectName);
    }

};

function runObjectTest9() {
    logMessage('--- Prototypal Inheritance via Object.create [ for details check JavaScript console ] ---');
    logMessage("Global Mammal Object created via String Literals:: type: " + Mammal.type + " - getDetails(): " + Mammal.getDetails());
    var MammalObj = Object.create(Mammal, {
        _objectName : { value: 'INITIAL MammelObj', writable: true},
        objectName: {
            configurable: true,
            get: function getObjectName () {
                logMessage('[MammalObj objectName get]:: ' + this._objectName );
                return this._objectName;
            },
            set: function setObjectName (value) {
                oldVal = this._objectName;
                this._objectName = value;
                logMessage("[MammalObj objectName set]:: NEW Value: " + this._objectName + " - Old Value: " + oldVal );
            }
         }
    });

    logMessage('[MammalObj.objectName]:: ' + MammalObj.objectName);
    MammalObj.objectName = "MammalObj";
    MammalObj.isWalking = false;
    logMessage("[MammalObj.getDetails()]:: " + MammalObj.getDetails());
    logMessage("[MammalObj.type]:: " + MammalObj.type);
    checkProperties(MammalObj);
    logMessage('------------------------------------------------------------------------');
}

Output: 
--- Prototypal Inheritance via Object.create [ for details check JavaScript console ] ---
Global Mammal Object created via String Literals:: type: Mammal - getDetails(): Type: Mammal - ObjectName:  Mammal BASE Object
[MammalObj objectName get]:: INITIAL MammelObj
[MammalObj.objectName]:: INITIAL MammelObj
[MammalObj objectName set]:: NEW Value: MammalObj - Old Value: INITIAL MammelObj
[MammalObj objectName get]:: MammalObj
[MammalObj.getDetails()]:: Type: Mammal - ObjectName:  MammalObj
[MammalObj.type]:: Mammal
[MammalObj objectName get]:: MammalObj
  • Output and Code for checkProperties(MammalObj) can be found here 

What happens with undefined properties and Methods  ?

  • Note we are uisng the above Objects Mammal and MammalObj
  • Referencing a NON existent function raises an Error:  TypError xxx.xxx  is not a function
  • Referencing a a NON existent property returns undefined
JavaScript Code:

logMessage('--- Testing TypeError: Object.function  is not a function ---');
try {
    logMessage("MammalObj.getAge():: " + MammalObj.getAge());
}   catch (e) {
    logMessage("FATAL ERROR:: Catching Exception running MammalObj.getAge() :: " + e);
}
logMessage("MammalObj.age :: " + MammalObj.age);
checkProperties(MammalObj);
logMessage('------------------------------------------------------------------------');

Output: 
--- Testing TypeError: Object.function  is not a function ---
FATAL ERROR:: Catching Exception running MammalObj.getAge() :: TypeError: MammalObj.getAge is not a function
MammalObj.age :: undefined
[MammalObj objectName get]:: MammalObj

 

Overwrite Object Methods

  • Note we still use  the object definition from our first sample for Mammal and MammalObj
JavaScript Code:

logMessage('--- Testing Overwrite Methodes ---');
var CatObj =  Object.create(MammalObj);
CatObj.legs =4;
CatObj.canWalk = function ()  {
    return("I've " + this.legs + " legs and can walk" );
};
CatObj.getDetails = function ()  {
    var myType = this.type;
    var myLegs = this.legs;
    return("[CatObj] - Hi, I'm a " + myType + " and I've " + myLegs + " legs and can walk" );
};
logMessage("catObj.canWalk():: " + CatObj.canWalk());
logMessage("catObj.getDetails():: " + CatObj.getDetails());
checkProperties(CatObj);
logMessage('------------------------------------------------------------------------');

Output: 
--- Testing Overwrite Methodes ---
catObj.canWalk():: I've 4 legs and can walk
catObj.getDetails():: [CatObj] - Hi, I'm a Mammal and I've 4 legs and can walk
[MammalObj objectName get]:: MammalObj


 Stripped Output from checkProperties(CatObj);

--- Detailed ProtoType Function walking down the Prototype Chain - Level:  1---
    ...
--- Detailed ProtoType Function walking down the Prototype Chain - Level:  2---
    ...
--- Detailed ProtoType Function walking down the Prototype Chain - Level:  3---
    ...
      
       [Mammal BASE Object, level:3][Found a FUNCTION] - Function Name: getDetails - Type: function - getPrototypeOf: function () {} - Constructor: function Function() { [native code] }
       [Mammal BASE Object, level:3] - Data Descriptor: Configurable: true - Enumerable: true - Writable: true - Value: function () {
        return("Type: " + this.type + " - ObjectName:  "  + this.objectName);
    }
   
   [CatObj, level:1][Found a FUNCTION] - Function Name: getDetails - Type: function - getPrototypeOf: function () {} - Constructor: function Function() { [native code] }
   [CatObj, level:1] - Data Descriptor: Configurable: true - Enumerable: true - Writable: true - Value: function ()  {
        var myType = this.type;
        var myLegs = this.legs;
        return("[CatObj] - Hi, I'm a " + myType + " and I've " + myLegs + " legs and can walk" );
    } 
  • There is a getDetails methode implemented by CatObj and Mammal BASE Object
  • The first method from catObj [ CatObj, level 1 ] is picked up when walking down the Prototype Chain

Modifying an underlying Object down in the Prototype Chain

  • Let’s create 2 cats named Fini and Felicitas based on CatObj
  • Change the type to Fish and modify their number of legs to 2
JavaScript Code : 

var CatObj =  Object.create(MammalObj);
CatObj.legs =4;
CatObj.objectName = 'CatObj';
CatObj.type = 'CAT';
CatObj.canWalk = function ()  {
    return("I've " + this.legs + " legs and can walk" );
};
CatObj.getDetails = function ()  {
    return("[CatObj] - Hi, I'm a " + this.type + " and I've " +  this.legs + " legs and can walk" );
};
logMessage("CatObj.getDetails():: " + CatObj.getDetails());
checkProperties(CatObj);
logMessage('------------------------------------------------------------------------');

logMessage('--- Modifying an underlying Object down in the Prototype Chain ----');
var felicitas =  Object.create(CatObj);
felicitas.age = 11;
felicitas.name = 'Felicitas';
logMessage("felicitas.getDetails():: " + felicitas.getDetails());

var fini = Object.create(CatObj);
fini.age = 9;
fini.name = 'Fini';
logMessage("fini.getDetails():: " + fini.getDetails());

logMessage("  ------ Lets modify all Cats to become a FISH type with 2 Legs ! ----- ")
CatObj.type = 'FISH';
CatObj.legs = 2;

logMessage("felicitas.getDetails() after morphing  Cats to a Fish :: " + felicitas.getDetails());
logMessage("fini.getDetails() after morphing Cats to a Fish:: " + fini.getDetails());
logMessage('------------------------------------------------------------------------');
 
Output : 
--- Modifying an underlying Object down in the Prototype Chain ----
felicitas.getDetails():: [CatObj] - Hi, I'm a CAT and I've 4 legs and can walk
fini.getDetails():: [CatObj] - Hi, I'm a CAT and I've 4 legs and can walk
  ------ Lets modify all Cats to become a FISH type with 2 Legs ! ----- 
felicitas.getDetails() after morphing  Cats to a Fish :: [CatObj] - Hi, I'm a FISH and I've 2 legs and can walk
fini.getDetails() after morphing Cats to a Fish:: [CatObj] - Hi, I'm a FISH and I've 2 legs and can walk

 

Directly Calling Methods from a Prototype Object

  • Calling Methods from a Prototype Object may return undefined
  • You should use call() or apply() to provide the correct THIS context [ felicitat Object ]
  • felicitas.getDetailsFromCat() fails because THIS context is pointing to the CatObj Object which doesn’t have properties this.name and  this.age
JavaScript Code:

    logMessage('--- Directly Calling Methods from a Prototype Object---');
    CatObj.type = 'CAT';
    CatObj.legs = 4;
    CatObj.getDetails = function ()  {
        return("Hi, my name is " +  this.name + " and I'm " + this.age + " years old. I'm a " + this.type
        + " and I've " + this.legs + " legs and can walk" );
    };
    var felicitas =  Object.create(CatObj);
    felicitas.age = 11;
    felicitas.name = 'Felicitas';
    felicitas.fObjectName = 'felicitas';

    felicitas.getDetailsFromCat = function () {
        CatObj.getDetails();     // this context is pointing to CatObj having NO this.name and this.age property
    };
    logMessage("felicitas.getDetailsFromCat() :: " +  CatObj.getDetails() );

    felicitas.getDetailsFromCat2 = function () {
        // this context now pointing to felicitas having this.name and this.age property
        logMessage("felicitas.getDetailsFromCat2() :: " +  CatObj.getDetails.call(this) );
    };
    felicitas.getDetailsFromCat2();
    logMessage("felicitas.getDetails():: " + felicitas.getDetails());
    logMessage('------------------------------------------------------------------------');

Output:  
  --- Directly Calling Methods from a Prototype Object---
felicitas.getDetailsFromCat() :: Hi, my name is undefined and I'm undefined years old. I'm a CAT and I've 4 legs and can walk
felicitas.getDetailsFromCat2() :: Hi, my name is Felicitas and I'm 11 years old. I'm a CAT and I've 4 legs and can walk
felicitas.getDetails():: Hi, my name is Felicitas and I'm 11 years old. I'm a CAT and I've 4 legs and can walk


 

References

Leave a Reply

Your email address will not be published. Required fields are marked *