A class is a blueprint for something you want to model. You can use a class to create many similar objects. In JavaScript, a class is simply a function (or a constructor) that creates objects where each object created from the constructor function will have the same properties. Additional properties can be added to each object as well. I will be using the following class for the first couple of examples.
In addition, all examples are written using the pseudoclassical instantiation pattern.
var SuperClass = function() {
this.prop1 = 50;
};
SuperClass.prototype.superMethod = function() {
console.log('This is a super method.');
};
A subclass is a class that inherits all the properties of another class, and at the same time, the subclass can add or modify properties. Therefore, a property can be defined in the parent class and shared with the subclass without having to define that property over again in the subclass. Any failed lookups of properties on the subclass will delegate up the prototype chain of parent classes. This is done by setting the subclass's prototype
to point to the prototype chain of its parent.
Before we look at the correct way to create subclasses, let's look at the wrong ways first.
var SubClass = function() {
this.prop2 = 100;
};
SubClass.prototype = SuperClass.prototype;
SubClass.prototype.superMethod = function() {
console.log('This is a sub method.');
};
var superObj = new SuperClass();
superObj.superMethod(); // logs 'This is a sub method.'
var subObj = new SubClass();
subObj.superMethod(); // logs 'This is a sub method.'
In the above example, SubClass
's prototype is being assigned to the same object as SuperClass
's prototype. If a property is then defined on SubClass.prototype
with the same name as a property in SuperClass
—superMethod
in the example—then it will override that property in SuperClass
, and it would affect all instances of SuperClass
. Again, this is an example of how not to create subclasses.
Another common mistake that is still being used is the following:
var SubClass = function() {
this.prop2 = 100;
};
SubClass.prototype = new SuperClass();
Sure, this works. Calling new SuperClass()
will create a new object that delegates to SuperClass.prototype
. However, using this pattern is not best practice, and there will be times where this will not work. For example, what if we slightly change SuperClass
to take in an argument and use it during instantiation?
var SuperClass = function(value) {
this.prop1 = value * 2;
};
var SubClass = function() {
this.prop2 = 100;
};
SubClass.prototype = new SuperClass();
var subObj = new SubClass();
console.log(subObj.prop1); // logs NaN
Notice that when calling subObj.prop1
, it didn't return a number. This breaks our expectation on having a number for the property and may even break functionality in the application.
Even if SuperClass
doesn't make use of any arguments, it is still a bad practice to set the prototype as a new instance of the parent class because doing so would create exactly that, a new instance of the parent class. Why create another object when you're not going to interact with it?
So here is the correct way to create subclasses as of ES5.1 (ECMA-262):
var SuperClass = function(value) {
this.prop1 = value * 2;
};
SuperClass.prototype.superMethod = function() {
console.log('This is a super method.');
};
var SubClass = function(value) {
SuperClass.call(this, value);
this.prop2 = 100;
};
SubClass.prototype = Object.create(SuperClass.prototype);
SubClass.prototype.constructor = SubClass;
SubClass.prototype.superMethod = function() {
console.log('This is a super method on sub.');
};
SubClass.prototype.subMethod = function() {
console.log('This is a sub method.');
};
var superObj = new SuperClass(25);
var subObj = new SubClass(30);
console.log(superObj.prop1); // logs 50
superObj.superMethod(); // logs 'This is a super method.'
console.log(subObj.prop1); // logs 60
console.log(subObj.prop2); // logs 100
subObj.superMethod(); // logs 'This is a super method on sub.'
subObj.subMethod(); // logs 'This is a sub method.'
Key takeaways about this approach:
- When instantiating a new instance of
SubClass
, it is not going to instantiate a new instance ofSuperClass
subObj.prop1
evaluates correctly because whenSubClass
creates a new instance, it is invoking theSuperClass
function withcall()
and passing invalue
superMethod
onSubClass
does not overridesuperMethod
onSuperClass
- A new instance of
SuperClass
is not created when settingSubClass.prototype
butSuperClass
's prototype chain is still passed toSubClass
's prototype SubClass.prototype.constructor
is set to refer to theSubClass
constructor function because otherwise, it would use the same constructor function asSuperClass
since that's the constructor inSuperClass.prototype
(remember, failed lookups on the subclass will delegate up the prototype chain)
That's it. Look out for ES6. It introduces the class
keyword for creating classes.