TOC
Objects:

Prototypes: Inheritance & extension

In the previous article, we introduced you to the concept of prototypes and the prototype chain in general. We saw how members were inherited through the prototype chain but in this article, we'll dig a bit deeper on the concept of prototypical inheritance and how we can use it in combination with existing objects to extend the functionality.

Extending objects

First, let's see how we can use the prototype system to extend an existing object. This could be your own object, but for this example, I'll demonstrate it by extending an existing, built-in object: The Array object.

Now, the Array object already comes with SO many useful methods, as we covered in the article on Array methods, so instead of coming up with a useful method, we'll add a silly one, just to show how easy it can be done. So without further ado, allow me to introduce the hasCat() method, which will tell you whether your array contains a cat or not:

Array.prototype.hasCat = function()
{
	return this.includes("Cat");
};

I extend the existing Array object by referencing Array.prototype and then I simply declare the function with the name hasCat. Notice that inside the function, I can reference the instance of the Array by using the this keyword, allowing me to call the includes() method on it, which will do all the "hard" work of checking whether the array contains a cat or not.

Now let's try using our new method:

Array.prototype.hasCat = function()
{
	return this.includes("Cat");
};

let animals = 
[
	"Dog",
	"Cat",
	"Mouse"
];

alert("Array contains a cat: " + animals.hasCat());

Notice how I declare a regular array, as we've done many times in this tutorial already, but on this array instance, I now have access to our hasCat() method. After it has been declared, all your arrays have this new method available. This can be really useful whenever you want to add your own, custom functionality to the existing JavaScript objects, or to objects from libraries you use etc.

Prototypical inheritance

As an alternative to extending existing objects, you may chose to simply create your own version of it. This can be particularly useful if you don't want to clutter existing objects, or if you want to make drastic changes to the underlying functionality which wouldn't be useful in all scenarios.

For instance, the above example, where we add the hasCat() method to the built-in Array object might not make sense for ALL arrays - perhaps you need a specific type of array for handling animals? You could of course build it from scratch, but that would be a LOT of work, and there's really no need to. Instead, we can just make a new version of the array object, which uses prototypical inheritance from the built-in Array object.

This is also used a lot to make more specific versions of another object. For instance, you may create an Animal object and then extend it into e.g. a Dog and/or a Cat object, which inherits the general behavior from the Animal object, while allowing you to add dog or cat specific behavior to the new objects.

Here's an example:

let animal = 
{
	numberOfLegs: 0,
	greet: function() 
	{ 
		return "Hello, I'm an animal! I have " + this.numberOfLegs + " legs...";
	}
};

let dog = {};
Object.setPrototypeOf(dog, animal);

dog.numberOfLegs = 4;
dog.bark = function()
{
	return "Woof!";
};

alert(animal.greet());
alert(dog.greet());
alert(dog.bark());

So, we have a generic Animal object, which defines an amount of legs and a function for greeting the world. We then create a Dog object and use the setPrototypeOf() method to set the prototype of the dog object to the animal object, basically inheriting its members.

After that, we modify the numberOfLegs property and then we add a dog-specific Bark() method to the Dog object - this method won't exist on the Animal object, only on the Dog object, where it belongs.

As you can see, it's very easy to build upon an existing object, thanks to prototypical inheritance. At this point, you could add a Cat object, based on the Animal object, or if you wanted to be even more specific, you could add a specific dog race, which could then be based on (and inherit from) the original Dog object.

What is "this"?

In the above example, you'll notice my use of the "this" keyword. As we previously talked about, when the interpreter meets "this", it always refers to the object which currently handles the execution.

But what happens when a method is shared between two different objects, thanks to inheritance, as we have just seen with the greet() method? You'll notice that in this method, I access this.numberOfLegs in the greet() method, but luckily for us, JavaScript handles this in the optimal way: While the method is indeed shared, the numberOfLegs property can have different values across the animal object and objects inheriting from it.

This also means that when the animal object uses this.numberOfLegs, even after we write to dog.numberOfLegs, they are two separate values and JavaScript always knows whether this is a reference to animal or dog or any other object inheriting from one of them.

Object.setPrototypeOf() vs. Object.create()

In the example above, I use the Object.setPrototypeOf() method to take a newly created object called dog and assign the animal object as its prototype. I use this approach because it clearly illustrates what happens, but as an alternative, we could have used the Object.create() method. It will allow us to both create the dog object AND assign its prototype in a single line, like this:

let dog = Object.create(animal);

The first parameter of the create() method is the prototype, in this case animal. According to the documentation, the Object.create() approach is actually preferable, because it allows for better optimization of your code. The only drawback is the lack of support in legacy browsers like Internet Explorer 8 and older, but since they are not supported by Microsoft any longer, this is not a major issue.

Summary

In the last couple of articles, we have talked about object prototypes, the prototype chain and prototypical inheritance. This system is the model JavaScript originally used, as an alternative to the older, object oriented class-based approach you'll find in many programming languages, both old and new.

However, in more recent versions of the EcmaScript specification, which JavaScript is built upon, classes have been introduced, probably to please programmers coming from class-based programming languages. The JavaScript class model is actually just an abstraction of the prototype system, so you can accomplish the same things no matter which model you prefer, but if you're familiar with classes, it might be easier for you to understand and use classes in JavaScript instead of relying directly on the prototype model.

So, if you already know about classes, or if you want to learn what they are all about, I suggest that you have a look at the next chapter, where we'll go through all aspects of this recent addition to the JavaScript language.