Table of Contents
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
- Javascript prototypes as abstract objects http://lab.la-grange.ca/javascript-prototypes-as-abstract-objects
- Fluent 2013 – Eric Elliott, “Classical Inheritance is Obsolete: How to Think in Prototypal OO”
https://www.youtube.com/watch?v=lKCCZTUx0sI - Douglas Crockford:: Prototypal Inheritance in JavaScript http://javascript.crockford.com/prototypal.html