JavaScript Inheritance

Inheritance in JavaScript is an interesting and important topic. But it can sometimes be a bit tricky to grasp because JavaScript is quite a bit different from class-based languages like C++ and Java. In what follows, we’ll look at how to do inheritance in JavaScript and some things to watch out for. Before you dive into this you should have a decent understanding of the basics of JavaScript and JavaScript objects. W3 Schools has a really good quick introductory overview of JavaScript (complete with examples and exercises that you can try yourself) that can be found here.

There are many different ways to create objects in JavaScript because, well, pretty much everything is an object. However, for our purposes here when we’re discussing objects we mean the generic “Object” in JavaScript, the dynamic type that we can throw any data that we want into in a collection of key-value pairs. And taking it a step further, when we say generic “Object” what we’re really talking mostly are functions and object literals. These will be the center of our focus when discussing inheritance in JavaScript.

An empty generic “Object” object can be created in JavaScript by doing the following…

var animal = new Object();

But objects are often initialized with data. This can be done using the Object literal approach as shown below…

var Animal = {
    color: "brown",
    size: "medium"
    age: 1,
    sound: function() {
        console.log('Grunt');
    }
    move: function() {
        console.log('Running');
    }
}

We can accomplish the same thing that we did above by creating a function and then assigning properties to the function…

function Animal() {
    this.color = "brown",
    this.size = "medium"
    this.age = 1,
    this.sound = function() {
        console.log('Grunt');
    }
    this.move = function() {
        console.log('Running');
    }
}

NOTE: The function and the object literal are actually not *exactly* the same. There *are* some differences between function Animal() { } and var Animal = function() { }; mainly having to do with how and when JavaScript executes these functions at runtime. You can, for example, call function Animal() elsewhere in your code before you declare the function, but you cannot call var Animal = function() { } before you declare it. You will get an error. It’s treated as the same situation as trying to use a variable before defining it. This is due to something called “hoisting” which I recommend doing a search for if you are interested in the technical side of it.

The benefit of using a function is that we can create a new instance of different Animals if we want.

var dog = new Animal();
var cat = new Animal();
console.log(dog.size);
console.log(cat.size);

There’s a problem with this though. All the animals that we create all have the same size, color, age, etc. We’re going to want our animals to all contain different types of data. To do that, we can slightly modify our function such that we pass arguments into the constructor.

function Animal(color, size, age, sound, move) {
    this.color = color,
    this.size = size
    this.age = age,
    this.sound = function() {
        console.log(sound);
    }
    this.move = function() {
        console.log(move);
    }
}

Now we’re getting somewhere. We have something that’s kind of similar to a class. It’s still not the same as a true class that’s you’d see in a language like Java, but this “psuedoclass” is the closest we’re going to get to a class in JavaScript. We can create new instances of a function (using the “new” keyword — kind of like you’d do in a class based language like Java or PHP) and give it the values we want.

That’s all well and good. But to implement something like inheritance in JavaScript using our psuedoclass function objects, we’d need an inheriting function to somehow contain values that it’s getting from another function in addition to the properties and methods we are setting by passing arguments into the constructor. To accomplish this, we first need to switch gears and talk about prototypes in JavaScript.

Prototypes

As it turns out, JavaScript is actually using inheritance all over the place because it is a prototype-based language. What does that mean? It means that every object in JavaScript inherits from another object that it uses as a prototype to create it. Consider the following JavaScript.

var hello = "I am happy to say hello!";
console.log(hello);

Okay here we have created a variable named “hello” and we’ve assigned a string to it. Simple enough right? Well what has actually happened is that JavaScript has used the “String” object to create the variable “hello”. This string object has a bunch of different properties and methods on it. One of these is “length”

console.log(hello.length);

This outputs the number of characters in our string. That length property is defined in the prototype of the String object in JavaScript so that all instances of strings get that property as well when they are created. What’s more, the prototype property of String is actually available to us and we can extend it! We can actually add methods directly to the String object prototype…

String.prototype.sayHi = function() {
    alert("Hi");
};
hello.sayHi();

Even though we created our “hello” variable before we added the function to the String object, our method still gets added to all the string objects in our application.

We could use prototypes to do something similar to what we did above to functions (which again, are also just objects)…

function Animal(){
}
Animal.prototype.color = "brown";
Animal.prototype.size = "medium";
Animal.prototype.age = 1;
Animal.prototype.sound = function(){
    console.log('Grunt');
};
Animal.prototype.move = function(){
    console.log('Run');
};

Now every instance of animal will get these properties and methods. We can instantiate as many “Animals” as we want and we don’t have to set the values for each Animal by passing arguments to the constructor every time. They’re just there now. You could even consolidate everything into a single object literal prototype property. The code above could be rewritten as the following…

function Animal(){
}
Animal.prototype = {
    color: "brown", 
    size: "medium",
    age: 1, 
    sound: function(){
        console.log('Grunt');
    },
    move: function(){
        console.log('Run');
    }
};

Now you can create objects and all of those properties are available…

var animal1 = new Animal();
var animal2 = new Animal();
animal1.sound();
console.log(animal2.age);

Inheritance

Now that we have looked at the constructor pattern and the prototype pattern to create objects we can now look at how we might be able to have objects inherit from other objects. Let’s take a look at what we had before…

function Animal(){
}
Animal.prototype = {
    color: "brown", 
    size: "medium",
    age: 1, 
    sound: function(){
        console.log('Grunt');
    },
    move: function(){
        console.log('Run');
    }
};

All of that is familiar. But what if we were to do something like the following…

function Cat(){
}
Cat.prototype = new Animal();

Whoa! We can do that? We sure can. Because the prototype items exist on “Animal,” “Cat” is going to inherit all of those properties when we set Cat’s prototype to a new instance of “Animal.”

var fluffy = new Cat();
console.log(fluffy.size);
var socks = new  Cat();
console.log(socks.size);

There’s one additional thing we want to do though just to keep things in order. Because Cat.prototype is now set to an instance of Animal, the constructor property of Cat now points to the Animal function and not the Cat function. So we’ll want to make sure that we set the constructor as pointing to Cat. This is a good practice to follow because, although the constructor property on the prototype is just a pointer to the Object that it was created from, it’s possible that somewhere in your code the “constructor” property is used. This is not very common, but it’s something we’ll do just in case. The way we do this is that after we have set our prototype to a new instance of our parent type, we can just set the constructor property back to our original function.

Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

Below is a visual representation of how inheritance works in JavaScript (that I stole off the Internet). It shows the relationship between parent objects and inheriting objects and their prototypes if we did the following…

function Foo() { }
Foo.prototype.x = 10;
Foo.prototype.calculate = function(val) {
    return this.x + val;
};
var b = new Foo();
var c = new Foo();
b.y = 20;
c.y = 30;

What this looks like in memory within your browser is shown below…

Problems With Reference Types

If your objects just contained primitive types like strings, booleans, and numbers, then everything in our JavaScript inheritance chain would be fine. However there is a problem that arises with reference types such as arrays and objects. Consider the following if we wanted to have each Animal object get its own separate description set…

function Animal(){
    this.description = ['cute', 'furry'];
}
Animal.prototype = {
    color: "brown", 
    size: "medium",
    age: 1, 
    sound: function(){
        console.log('Grunt');
    },
    move: function(){
        console.log('Run');
    }
};
function Cat() { }
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat();
var cat2 = new Cat();
cat1.description.push('smelly');
console.log(cat2.description); // ['cute', 'furry', 'smelly']

You see how cat2 got the new trait even though we intended to only add it to cat1? When you change a reference type on an object, the change shows up in all of the objects. How can we prevent this?

Better Inheritance: Constructor Stealing

As it turns out there is a good quick fix to this known as the “constructor stealing” method. Basically all you have to do is run SuperType.call(this) in the function SubType…

function SubType(){
    SuperType.call(this);
}

So with our example it would look like the following…

function Animal(){
    this.description = ['cute', 'furry'];
}
Animal.prototype = {
    color: "brown", 
    size: "medium",
    age: 1, 
    sound: function(){
        console.log('Grunt');
    },
    move: function(){
        console.log('Run');
    }
};
function Cat() { 
    Animal.call(this);
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat();
var cat2 = new Cat();
cat1.description.push('smelly');
console.log(cat2.description); // ['cute', 'furry']

Now we can see that each individual cat object gets their own description set.

Multiple Levels of Inheritance

We can take inheritance in JavaScript as many levels deep as we want, just like we’d do in any other language. We just need to be sure to set the inheritance chain up in the proper order…

function Animal() {
}
Animal.prototype = {
    color: "brown", 
    size: "medium",
    age: 1, 
    sound: function(){
        console.log('Grunt');
    },
    move: function(){
        console.log('Run');
    }
};
function Dog() {
    Animal.call(this);
}
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
function Rottweiler() {
    Dog.call(this);
}
Rottweiler.prototype = new Dog();
Rottweiler.prototype.constructor = Rottweiler;
function Pomeranian() {
    Dog.call(this);
}
Pomeranian.prototype = new Dog();
Pomeranian.prototype.constructor = Pomeranian;
Pomeranian.prototype.sound = function(){
    console.log('Yap Yap Yap');
}
Rottweiler.prototype.sound = function(){
    console.log('Woof');
}
var zeus = new Rottweiler();
var poopsy = new Pomeranian();
zeus.sound();
poopsy.sound();

Conclusion

So there you have it! JavaScript inheritance. Inheritance in JavaScript is a subject that could be explored even further. There are even other methods and ways you can set up inheritance in your JavaScript applications. It all depends on what you are looking to accomplish. But if you can get a good grasp on understanding what was discussed thus far in this introductory overview, hopefully you’re off to a good start.

9bit Studios E-Books

Like this post? How about a share?

Stay Updated with the 9bit Studios Newsletter

0 Responses to JavaScript Inheritance

    Leave a Reply

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