Tuesday, November 5, 2024

JavaScript Object Chaining using Prototypal Inheritance

Inheritance is a key aspect of object-oriented programming, whereby a new class is created by extending an existing one. The main purpose of this process, called subclassing, is to be able to create progressively more specialized classes by building on existing ones. JavaScript implements its own hierarchy model using prototypes. In today’s article, we’re going to learn how to use the prototype and constructor properties to trace an object’s ancestry up to the base Object. Highly useful!

Referencing an Object’s Parent

In all object-oriented languages, there is always a way to get a class’s parent. In Java, for example, it’s via the super pointer. As the following code demonstrates, the Student subclass can call the Person constructor directly to set its inherited name property:

public class Person
{
  private String name;

  public Person(String initialName)
  {
    name = initialName;
  }
}

public class Student extends Person
{
  private int studentNumber;

	//we can call the parent's constructor via the "super" pointer
  public Student(String initialName, int initialStudentNumber)
  {
    super(initialName);
    studentNumber = initialStudentNumber;
  }
}

If the Person had a setter() for name, we could call it instead using:

super.setName(initialName);

 

The Object Constructor Function

The super pointer is not just limited to a class’s immediate parent. It can be used to trace the object chain from the lowest level subclass all the way up to the Object superclass. JavaScript also provides a mechanism for following the ancestry of an object up to the Object, but, instead of the super, it is the object constructor that holds the reference to the parent. The constructor plays a pivotal role in object creation in that it IS the function which created the object. All objects have a constructor function because ALL objects are functions in JavaScript. As such, even {}.constructor returns a function! Having said that, there is a distinct “object” type in JavaScript. The typeof() function can distinguish between the two, as the following two lines of code demonstrate:

alert(typeof( function() {}));      //displays "function"
alert(typeof( new function() {}));  //displays "object"

The key difference between the two is the inclusion of the “new” keyword that creates an object. Here again, typeof((new function() {}).constructor) still returns the default Object constructor function

Using prototypal inheritance, objects can still inherit from other objects. However, there is no specific keyword or operator – such as “extends” in Java – to achieve that end. What happens is that when you instantiate an object using the “new” keyword, “new myObject()” produces a new object that inherits from myObject.prototype rather than directly from the object itself. According to renown JavaScript guru Douglas Crockford:

This indirection was intended to make the language seem more familiar to classically trained programmers, but failed to do that, as we can see from the very low opinion Java programmers have of JavaScript. JavaScript’s constructor pattern did not appeal to the classical crowd. It also obscured JavaScript’s true prototypal nature. As a result, there are very few programmers who know how to use the language effectively.

Luckily, it isn’t all bad news. We can follow the inheritance chain by keeping track of the parent within the child object. There are many ways to go about this. Some people have chosen to emulate the classical inheritance model of Java by adding a “super” or “superclass” property to the child object. In the following three level example, the parent’s constructor function is stored:

function Person(name) {
  if ( arguments.length > 0 )
    this.init(name);
}

Person.prototype.init = function(name) {
    this.name = name;
}

Employee.prototype = new Person();
Employee.prototype.constructor = Employee;
Employee.constructor = Person.prototype.constructor;

function Employee(name, id) {
    if ( arguments.length > 0 ) {
        this.init(name);
        this.id = id;
    }
}

Manager.prototype = new Employee;
Manager.prototype.constructor = Manager;
Manager.constructor = Employee.prototype.constructor;

function Manager(name, id, department) {
    if ( arguments.length > 0 ) {
        this.init(name);
        this.department = department;
    }
}

The Effects of Object Creation Methods on Debugging

In the JavaScript Prototypal Inheritance Explained article, we saw several ways to create an object. While all are valid, be aware that the style of object creation that you use will have an effect on debugging. Anytime you instantiate a new object without an explicit constructor, the JavaScript interpreter will use the default. This is also true of the base Object constructor. In fact, there’s no indication of who the constructor’s owner is because it doesn’t contain the object name (in this case “Object”) in it!

document.writeln(Object.constructor.toString());
//outputs "Function() { [native code] }"

Therefore, consider naming any function that may potentially be called more than once:

//avoid this if possible!
var myFunc = function() {
  alert('Hello from the ' + arguments.callee.functionName() + '() function!');
	//outputs "Hello from the Anonymous() function!". Not much help!
};

//while redundant, the following is perfectly legal
var myFunc = function myFunc() {
  alert('Hello from the ' + arguments.callee.functionName() + '() function!');
	//outputs "Hello from the myFunc() function!"
}

This functionName() utility method can be applied to any “function” type to parse the name from the function code:

Function.prototype.functionName = function()
{
  var name=/W*functions+([w$]+)(/.exec(this);
  if(!name) return 'Anonymous';
  return name[1];
}
alert(myFunc.functionName());  //outputs myFunc"
}

One of the uses for the functionName() Function method is to follow an object chain via each object’s function constructor property. Our getInheritanceChain() Object method follows the prototype chain all the way to the base Object by looking for our overridden Object.constructor.toString()’s return value of “Object”. Upon exiting the loop it then pushes the “Object” string onto the inheritanceChain array:

Object.constructor.toString = function() { return "Object"; }
Object.prototype.getInheritanceChain = function() {
	var parent = this.constructor;
	var inheritanceChain = new Array();
	
	while ( parent != 'Object' ) {
		inheritanceChain.push( parent.functionName() );
		parent = parent.constructor;
	}
	inheritanceChain.push( parent ); //this is the Object superclass
	
	return inheritanceChain.join(", ");
}

var manager = new Manager("Bob Smith", 1234, "finance");
document.writeln('I am '+manager.name+', a '+Manager.functionName()+', and I extend '+Manager.getInheritanceChain());
//outputs "I am Bob Smith, a Manager, and I extend Employee, Person, Object"

Conclusion

Although not without its faults, the JavaScript prototypal hierarchy model can be made to do do what we want it to. Dissatisfied with the differences between prototypal and classical OOP inheritance, many JavaScript developers have devised ways to mimic the latter. We’ll be taking a look at those efforts shortly.

Robert Gravelle
Robert Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Popular Articles

Featured