Class inheritance?
So I've been noticing that a lot of people I know never use class inheritance in Javascript. Coming from a C++, PureBasic, Lua and Assembly world I was extremely happy when I was first introduced to object oriented programming in C++. I hope that I can make inheritance in JS clear to people who know JS OOP, but don't know inheritance yet.
Object Oriented Programming
A quick short explanation on OOP from Wikipedia:
Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which are data structures that contain data, in the form of fields, often known as attributes; and code, in the form of procedures, often known as methods. A distinguishing feature of objects is that an object's procedures can access and often modify the data fields of the object with which they are associated (objects have a notion of "this"). In object-oriented programming, computer programs are designed by making them out of objects that interact with one another. There is significant diversity in object-oriented programming, but most popular languages are class-based, meaning that objects are instances of classes, which typically also determines their type
One of the features of class-based object oriented programming is that one class can inherit properties and methods from a superclass, its parent. Using this method you could for example create a Shape class, which contains properties regarding position and dimensions. This Shape class could also contain a render method, which when called, would render the shape onto a view.
This would be very useful, because recurring functionality and properties could be placed into one class, Shape. You could then write another class which would extend Shape and name it Ball. Ball now inherits all public and protected properties and methods from Shape and without any modification, acts pretty much the same as its superclass.
You could now override inherited properties and methods with custom ones. From within an overidden inherited method, you should still be able to call the original one from the superclass. Like this, you could literally extend behaviour with a new behaviour or completely override it, losing the old behaviour.
Simple Animal example
Let's make a very small and simple example that displays this paradigm in Javascript, where a superclass Animal contains the properties name, age and the method talk().
// Superclass Animal - constructor
function Animal(name, age) {
this.name = name;
this.age = age;
}
// talk method inherited by any child classes
Animal.prototype.talk = function() {
if(!this.sound) {
console.log("I don't know my sound yet! :(");
} else {
console.log(this.sound);
}
}
Simple enough if you know a little basic Javascript OOP. However, now I want to create a class that can produce objects of type Dog. This new class should inherit Animal's prototype and set a new constructor, which would point to Dog.
// child class 1: Dog
function Dog(name, age) {
// call the superclass constructor first
Animal.call(this, name, age);
this.sound = "Wraf!";
}
Dog.prototype = Object.create(Animal.prototype); // copy the prototype from Animal
// Dog is the constructor for this class,
// so make sure it doesn't think it should use Animal.prototype.constructor.
Dog.prototype.constructor = Dog;
We have now extended Animal with a new class named Dog. Let's make one more, and we name it Cat. Cat will have an overridden talk method, which makes the sound twice!
// child class 2: Cat
function Cat(name, age) {
// call the superclass constructor first
Animal.call(this, name, age);
this.sound = "Meow!";
}
Cat.prototype = Object.create(Animal.prototype); // again, copy the prototype!
Cat.prototype.constructor = Cat; // and the constructor should be Cat!
// override the talk method
Cat.prototype.talk = function() {
// I want to meow twice!
console.log(this.sound + " " + this.sound);
}
Awesome, we have our superclass now and two child classes which inherit properties and methods from Animal. So now we can create a few objects and try them out! Note how Dog doesn't have a talk method, but it actually is called in the code below. That's because Dog utilizes the inherited version of talk which can be found in Animal.
Here's some example implementation of our classes:
var ballou = new Dog("Ballou", 10);
var lady = new Dog("Lady", 11);
var missy = new Cat("Missy", 4);
ballou.talk(); // logs "Wraf!"
lady.talk(); // logs "Wraf!"
missy.talk() // logs "Meow! Meow!"
But, isn't there a simpler way?
Well, not directly. It's such a shame that Javascript (in the current widely implemented versions) doesn't have a simple extend keyword or a colon you could use to extend a class. Creating a prototype based on the superclass and overwriting the constructor is a requirement. You could wrap the logic in functions and make things a bit easier:
// an Object.create polyfill from MDN, but this polyfill does not support properties.
if (!("create" in Object) || typeof(Object.create !== "function")) {
Object.create = (function() {
var Object = function() {};
return function (prototype) {
if (arguments.length > 1) {
throw Error('Second argument not supported');
}
if (typeof prototype != 'object') {
throw TypeError('Argument must be an object');
}
Object.prototype = prototype;
var result = new Object();
Object.prototype = null;
return result;
};
})();
}
// a function to wrap the extend logic into one function
function extend(original, ctor) {
ctor.prototype = Object.create(original.prototype);
ctor.parent = original;
ctor.prototype.constructor = ctor;
return ctor;
};
// A method for all functions which allows extending by calling one function!
if(!('extend' in Function.prototype) || typeof(Function.prototype.extend) !== "function") {
Function.prototype.extend = function(ctor) {
return extend(this, ctor);
};
}
With this new code, let's have a look at what we could change in the Animals code earlier in the post. We don't have to manually set a prototype and constructor any more, and the extend function above also adds a parent property to the extension. Seems useful, doesn't it? The changes in extend code:
// child class 1: Dog
var Dog = Animal.extend(function Dog(name, age) {
Dog.parent.call(this, name, age);
this.sound = "Wraf!";
});
// child class 2: Cat
var Cat = Animal.extend(function Cat(name, age) {
// call the superclass constructor first
Cat.parent.call(this, name, age);
this.sound = "Meow!";
});
// override the talk method
Cat.prototype.talk = function() {
// I want to meow twice!
console.log(this.sound + " " + this.sound);
};
As you might notice, is that upon the moment of extension, Dog and Cat names appear twice in a single line of code. This is because the new classes are now assigned to a local variable. The second occurrence of the name makes sure the function (and thus class) is still named and not anonymous. We can now make scope-specific classes that inherit properties and methods from a superclass from any scope we can reach!
Testing the instance against a type (class)
Extending a class comes with an other extra feature, checking the type of an object. If you know the variable you want to check a type of is an object, you can use instanceof to test if an object is an instance of a class you specify. This test is checking if an object is a descendant of the class you test it against, anywhere up the hierarchy.
So from our Animal example if(ballou instanceof Dog)
would result in true, if(ballou instanceof Animal)
would also result in true and if(ballou instanceof Cat)
would not result in true. The last test tests an object of type Dog against Cat, they both inherit from Animal, but testing siblings against eachother will always fail.
You could use this test to keep a list of Animal objects (Cats or Dogs) which all talk differently, and simply test if each of the objects in the list is an instance of Animal. You can then safely call the talk method on each object.
One more example, back to the Shape!
Here's one more example. The code isn't optimized or supposed to be used in a live environment, it's meant to display the possibilities of extending a class. I suggest you open the pen in codepen, as reading the code in the small embedded pen is rather difficult. Also check your console, the code in the example pen logs some conditions!
Check out this pen.
Thanks for reading, it's been a long post! :)