Thursday, March 28, 2024

JavaScript Prototypical Inheritance Explained

JavaScript has long held the dubious distinction of being both one of the world’s most popular and most misunderstood programming languages. The source of the confusion is not that difficult to spot. It’s really in the name. JavaScript sounds a lot like a subset of Java, when it isn’t. Java is only one of the languages that JavaScript is modeled after, along with C and C++.

Moreover, despite its striking similarity to Object-Oriented (OO) languages, JavaScript itself eschews the classical inheritance models of the aforementioned languages in favor of prototypical inheritance. That can’t be helping.

In this tutorial, we’re going to learn more about prototypical inheritance and its ramifications in writing OO code. The eventual goal is to note how it differs from classical inheritance to determine whether or not the two can’t meet somewhere in the middle.

Objects Versus Classes

Strictly speaking, JavaScript is a class-less language. The Class keyword, although reserved, is not part of the language definition. JavaScript is built on Objects rather than Classes. Thus you can write

myObject=new Object();

but not

Class MyObject {}

as you would in Java. In traditional OOP, an object is an instance of a class, but in JavaScript, it takes on a specialized meaning because an Object acts as the definition of an object just like a class does. However, whereas a Class provides mechanisms for encapsulation and inheritance, a JavaScript Object doesn’t resemble a Class all that much. The ability to encapsulate its members is there, but you have to put some thought and effort into it. There is also the ability to inherit from other Objects, but JavaScript Objects use prototypical inheritance, which is quite different from Class inheritance. So what exactly is an Object in JavaScript? It is really just an unordered collection of key-value pairs. Anything that is not a primitive – undefined, null, boolean, number, or string – is an Object in JavaScript.

Creating an Object

Being a fancy free scripting language, JavaScript offers many ways to do the same thing. Take Object creation for instance. Besides the aforementioned and fairly explicit

myObject=new Object();

syntax, you can also use the shorthand object literal syntax, using curly braces:

var myObject={};

. You can even create an Object using a function:

function MyObject() {}
var myObject = new MyObject();

//this creates a singleton
var myObject = new function MyObject() {};

//you can test it as follows
alert(typeof(myObject)); //displays "object"

At least that looks a bit like Class syntax in that is uses the familiar “new” keyword.

Inheritance Using the Prototype Property

A prototype is an internal object from which other objects inherit properties. Its main purpose is to allow multiple instances of an object to share a common property. Thus, object properties which are defined using the prototype object are inherited by all instances which reference it.

How this works becomes quite clear when you see it in action. Say you add a method to MyObject (that’s the object, not the instance) called doSomething(). Would that cause new instances of MyObject to inherit the doSomething() method? No it would not. What that would do is create a static method that can be called without first instantiating the object (useful to know in itself!). Likewise, a function that is added to an instance would only apply to that particular instance. Only when the function is assigned to the MyObject.prototype is it inherited by all instances of that Object, including those we instantiated before the creation of the new function:

MyObject.doSomething = function() {
	alert("I do as I am told.");
}

var anotherObject = new MyObject();
anotherObject.doSomething = function() {
	alert("I do the bare minimum required of me!");
}

anotherObject.doSomething(); //calls the instance specific method
MyObject.doSomething(); //calls the static Object method
MyObject.prototype.doSomething = MyObject.doSomething; //apply the static method to all instances from here on in
anotherObject.doSomething();  //still calls the instance method
var yetAnotherObject = new MyObject();
yetAnotherObject.doSomething(); //inherited doSomething() from MyObject
myObject.doSomething(); //pre-existing instances also inherit the new function

Overriding Pre-existing JavaScript Methods

The general rule is that you can prototype any object that’s initialized with the “new” keyword, and that includes native JavaScript objects, such as Arrays, Dates, Strings, Functions, and even the generic Object itself.

In the following example, a function that returns the function name has been added to the Function prototype. An especially good use for this function is to get a function’s own name. The arguments.callee references the function object, so applying our functionName() function to it returns the function’s name:

Function.prototype.functionName = function()
{
  var name=/W*functions+([w$]+)(/.exec(this);
  return name?name[1]:'Anonymous';
}

function helloWorld() {
	alert('Hello from the '+arguments.callee.functionName()+'() function!');
}

helloWorld(); //displays "Hello from the helloWorld() function!"

Our final example of the day demonstrates how to extend an existing method’s functionality by reusing it. There are two snippets of code below. The first is a typical overridden Array join() function that I took off an online forum. The only difference between it and the original is that the default separator has been changed. Nonetheless, the contributors of the forum advocated completely rewriting the original join() method. If you ask me, replacing tried and true code with your own is just asking for bugs! A far better way is to use a closure to store the original method and add your own functionality to it:

//a complete (and unnecessary) rewrite
Array.prototype.join = function(separator) {
    if (separator == undefined) {separator = '|'}
    var text = new String();
    for (obj in this) {
      text += this[obj] + separator;
		}
		return text;
}

//a better way that reuses the existing functionality
Array.prototype.join = (function(originalJoin) {
	return function(separator) { return originalJoin.call(this, separator===undefined?separator:'|'); }; 
})(Array.prototype.join);

alert( [1,2,3,4,5].join() );      //uses the default: displays "1|2|3|4|5"
alert( [1,2,3,4,5].join('') );    //no separator:     displays "12345"
alert( [1,2,3,4,5].join(' * ') ); //custom separator: displays "1 * 2 * 3 * 4 * 5"

Conclusion

Part 2 of this tutorial will learn about prototype chaining and how to use it to trace an object’s ancestry back up to the base Object.

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