JavaScript Inheritance
There are several approaches to implementing JavaScript inheritance. Some follow JavaScript's object-based nature, while others achieve a “classical” inheritance closer to what many people may be familiar with from languages like Java or C++.
Some JavaScript libraries provide class-like behaviour, like MooTools‘ “Class” construct or Ext JS with Ext.Base/Ext.ClassManager. However, if you want to write code that does not depend on a library (like writing your own library), you'll probably end up implementing some sort of inheritance mechanism on your own. Like I said, there are many ways to achieve this, and they all have their advantages. Let me show you two ways of implementing inheritance in JavaScript. The first one being the way I guess it was intended by the language's designers, and the second one a solution for people who rely on more restrictive object scope.
How JavaScript Does It – The Pseudoclassical Way
In JavaScript, functions are objects and functions can also be used as constructors. By convention, function names that start with a capital letter are considered constructors and can be used with the keyword new to construct objects:
// Declaring our Person object
var Person = function(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
};
// Adding a couple of operations
Person.prototype.getFirstName = function() {
return this.firstName;
};
Person.prototype.setFirstName = function(firstName) {
this.firstName = firstName;
};
Person.prototype.getLastName = function() {
return this.lastName;
};
Person.prototype.setLastName = function(lastName) {
this.lastName = lastName;
};
Person.prototype.sayHello = function() {
return "Hello, I am a person and my name is "
+ this.firstName + " " + this.lastName;
};
// Constructing a couple instances
var buffy = new Person('Buffy', 'Summers');
var willow = new Person('Willow', 'Rosenberg');
console.log(buffy.getFirstName()); // Buffy
console.log(willow.getFirstName()); // Willow
// Setting a new first name for Buffy
buffy.setFirstName('Buffy Anne');
console.log(buffy.getFirstName()); // Buffy Anne
console.log(willow.getFirstName()); // Willow
We can easily create Person instances with the new keyword. If we change Buffy's first name to “Buffy Anne”, the object state of the buffy variable is changed, while the willow variable remains untouched. We clearly have two independent instances with their own object states.
So far, so good. But how do we extend the Person function? Let's say we want to create a Student function that inherits from Person. This is achieved fairly easily by overwriting the Student prototype, like so:
// Declaring our Student object
var Student = function(firstName, lastName, studentNumber) {
this.firstName = firstName;
this.lastName = lastName;
this.studentNumber = studentNumber;
};
// Overwriting the object's prototype with the parent prototype
// This is where we actually inherit from Person
Student.prototype = new Person();
// Adding getter and setter functions
Student.prototype.getStudentNumber = function() {
return this.studentNumber;
};
Student.prototype.setStudentNumber = function(studentNumber) {
this.studentNumber = studentNumber;
};
// Constructing a student
var xander = new Student('Xander', 'Harris', 1234);
console.log(xander.getFirstName()); // Xander
console.log(xander.getLastName()); // Harris
console.log(xander.getStudentNumber()); // 1234
We can even override parent operations using the .prototype syntax:
// Overriding a parent operation
Student.prototype.sayHello = function() {
return "Hello, I am a student and my name is "
+ this.firstName + " " + this.lastName;
};
console.log(buffy.sayHello()); // Buffy is a person
console.log(willow.sayHello()); // So is Willow
console.log(xander.sayHello()); // But Xander's a student
Static operations? No problem:
// Creating a factory method for Person
Person.create = function(firstName, lastName) {
return new Person(firstName, lastName);
}
var giles = Person.create("Rupert", "Giles");
console.log(giles.sayHello());
Calling super/parent operations? You got it:
Student.prototype.sayHello = function() {
return Person.prototype.sayHello.call(this) + ", and I'm also a student";
};
console.log(xander.sayHello());
I think it can rightfully be said that this pattern has treated us well thus far. We've got inheritance, dynamic binding, static operations and super operation calls covered. The syntax of declaring an object as a function and then implementing its operations outside the function body may seem a bit strange to people coming from languages like Java (and a lot less to those coming from C++). We could actually have implemented the functions within the function body, just like we did with the properties, using the thiskeyword. The problem is, with each instantiation, we would have created a new set of functions in memory, which is why this technique is generally frowned upon. There are two caveats, though:
- Since Person and Student are functions, they can be called without the new keyword. In that case, this is bound to the global object, resulting in errors when trying to use Person and Student variables as objects. The convention is to always capitalise constructor functions and use the new keyword with them, but there is no mechanism in JavaScript that prevents us from doing it the wrong way.
- All object properties are set to public and our getter and setter operations can easily be bypassed. We have no way to declare our properties (and operations) as private (or protected). And while that may not bother many my-code-is-the-design-hackers out there, proper encapsulation is still one of the pillars of object-oriented programming and design, and you should be aware of this, should you decide to use this pattern in your application or library.
Enter Functional Inheritance
The idea behind functional inheritance is to use a function that returns an object. Within that function we can have as many local (private) properties and operations as we wish. The object returned is actually the public interface. Let's see how it works. We're following the same steps as in the prototype example above, but we use a different pattern to construct our objects:
// Declaring our Person object
var Person = function(firstName, lastName) {
var _firstName,
_lastName,
self = {};
(function(){
_firstName = firstName;
_lastName = lastName;
})();
self.getFirstName = function() {
return _firstName;
}
self.setFirstName = function(firstName) {
_firstName = firstName;
};
self.getLastName = function() {
return _lastName;
};
self.setLastName = function(lastName) {
_lastName = lastName;
};
self.sayHello = function() {
return "Hello, I am a person and my name is "
+ _firstName + " " + _lastName;
};
return self;
};
// Constructing a couple instances
var buffy = Person('Buffy', 'Summers');
var willow = Person('Willow', 'Rosenberg');
console.log(buffy.getFirstName()); // Buffy
console.log(willow.getFirstName()); // Willow
// Setting a new first name for Buffy
buffy.setFirstName('Buffy Anne');
console.log(buffy.getFirstName()); // Buffy Anne
console.log(willow.getFirstName()); // Willow
The important thing to understand is, that every variable that gets declared following the var keyword is in the scope of the Person function. We're declaring two private properties for the person's first and last names. In addition, we declare a third variable called self, which is returned at the end of the function body, and therefore holds all our public members (properties or operations). self is similar to this in that respect.
Another thing to note is the anonymous self-executing function right after the variable declarations. It is executed automatically each time a Person object is created (read: the Person function is invoked) and serves as our constructor. It is not necessary to execute that code within a self-executing function, but it makes it easier to distinguish the constructor code from the properties and business logic.
To create a Student type that inherits from Person, we create a function similar to the Person one, with variable declarations at the top, a self-executing constructor function and a public interface object that gets returned at the end of the function body:
// Declaring our Student object
var Student = function(firstName, lastName, studentNumber) {
var _studentNumber,
parent = [],
self = {};
// Constructor
(function(){
self = Person(firstName, lastName);
parent.sayHello = self.sayHello;
_studentNumber = studentNumber;
})();
// Public interface
self.getStudentNumber = function() {
return _studentNumber;
};
self.setStudentNumber = function(studentNumber) {
_studentNumber = studentNumber;
};
// Overriding a parent operation
self.sayHello = function() {
return "Hello, I am a student and my name is "
+ self.getFirstName() + " " + self.getLastName();
};
// Calling a super operation from this object
self.sayHelloSuper = function() {
return parent.sayHello() + ", and I'm also a student.";
};
return self;
};
// Constructing a student
var xander = Student('Xander', 'Harris', 1234);
console.log(xander.getFirstName()); // Xander
console.log(xander.getLastName()); // Harris
console.log(xander.getStudentNumber()); // 1234
console.log(buffy.sayHello()); // Buffy is a person
console.log(willow.sayHello()); // So is Willow
console.log(xander.sayHello()); // But Xander's a student
console.log(xander.sayHelloSuper());
Unlike in the Person function's constructor, we do assign a value to the function's self object now: the return value of a Person() function invocation, meaning the entire public interface of the Person type. This is the line of code where Student actually becomes a subtype of Person. We're also assigning self.sayHello to the variable parent.sayHello, but we'll come back to this in a bit.
Other than that, the sayHello operation is overridden to match the Student type. In this pattern any operation is overridden by assigning a new operation to a variable, and it's all done within the type's function body. Please note, that a type's private data is indeed private and not protected. The Student type has no direct access to the Person type's _firstName or _lastName properties. Access is granted only by the public operations getFirstName etc.
Finally, the operation sayHelloSuper demonstrates a way to call parent operations from within the subtype. It's not the prettiest pattern, but in the constructor, we assign the Person.sayHello operation to a parentobject which is meant to hold selected parent operations for later reuse. Later in sayHelloSuper, we can call that operation and not the overridden one of the Student type.
Static operations work the same way as with the first pattern. They're simply added to the type function directly and can be used right away. Please note, that in neither pattern static operations are inherited by subtypes.
// Creating a factory method for Person
Person.create = function(firstName, lastName) {
return Person(firstName, lastName);
}
var giles = Person.create("Rupert", "Giles");
console.log(giles.sayHello());
Conclusion
We've only looked at two patterns to implement JavaScript inheritance. There's a couple more patterns, but I guess it's clear that there is no one perfect way to implement inheritance in JavaScript. All patterns have their own advantages and disadvantages.
Personally, I've been using the functional pattern the most in actual projects, because I believe proper data encapsulation is vital in most situations. However, apart from the missing private access, the pseudoclassical way of inheritance does have a certain elegance to it, is very memory efficient and it just feels right.