Introduction
In the second article of the series of "Exploring OOPS - JavaScript Style", we look at how inheritance is implemented in JavaScript.
Note: This article is an adaptation (may be a mirror) of the article originally posted at Edujiniā¢ Eduzineā¢ here.
All the three articles of the series are listed below:
Inheritance can be described as reusability by virtue of heritage. An inherited data-type automatically obtains the functionality defined in the data-types from which it inherits.
JavaScript supports single inheritance, i.e., you can inherit from maximum one type. Also, it does not support the concept of interface introduced in languages like Java, C# to support multiple inheritance. In JavaScript, what we have is instance-inheritance (or runtime inheritance) rather than class-inheritance (or compile-time inheritance), making inheritance more powerful than probably any other language around (barring the ones like Smalltalk)!
Getting Started
In Part 1, we defined UserProfile
with username
and password
. In this article, we create EmployeeProfile
that inherits from UserProfile
and adds employeeID
. It also reimplements (override, if you like to use that word) authenticate
method.
EmployeeProfile Definition
We start with defining EmployeeProfile
with employeeID
besides earlier properties. Create a file EmployeeProfile.js with the following code:
function EmployeeProfile(username, password, employeeID)
{
this.username = username;
this.password = password;
this.employeeID = employeeID;
}
The next step is to mark it inherited from UserProfile
. For this, we need to work with the property prototype
that is always available to us whenever we declare a function. (Note that all functions can be used as constructor.) Add the following additional code at the end of the code above:
EmployeeProfile.prototype = new UserProfile();
Note the following:
- The inheritance is defined not through the data-type
UserProfile
but through its instance. - The instance of
UserProfile
has been created without passing any parameters to the method.
Testing the Definition
So, it's time to test the definition so far... Let's create EmployeeProfile.html with the following content:
<html>
<head>
<title>OOPS in JavaScript- Encapsulation</code>
<script language="'javascript'" type='text/javascript' src='UserProfile.js'></script>
<script language="'javascript'" type='text/javascript' src='EmployeeProfile.js'></script>
<script language="'javascript'" type='text/javascript'>
function validateUser()
{
var eid = document.getElementById('i').value;
var uname = document.getElementById('u').value;
var pwd = document.getElementById('p').value;
var e = document.getElementById('result');
var ep = new EmployeeProfile(uname, pwd, eid);
e.innerHTML = 'Username: ' + ep.username
+ '<br/>Password: ' + ep.password
+ '<br/>EmployeeID: ' + ep.employeeID
+ '<br/>Authenticate: ' + ep.authenticate();
}
</script>
</head>
<body>
Employee ID: <input type='text' name='i' id='i' />
<br/>
Username: <input type='text' name='u' id='u' />
<br/>
Password: <input type='password' name='p' id='p' />
<br/>
<button onclick='validateUser(); return false;'>Login</button>
<div id='result'></div>
</body>
</html>
I think the code is self explanatory. What we get is not only the three properties but also automatic definition of authenticate
method for EmployeeProfile
. After all, that's what inheritance is all about. In the definition of EmployeeProfile
, we never mention about it but it is automatically available to it through its parent object UserProfile
.
instanceof Operator
We'll explore method overriding and other complex (unexplored, not mentioned so far) things in a while. We take a short while to look at instanceof
operator.
It is a binary operator. The left operand is the object-reference and the right operand is the function-definition (data-type reference). The operator returns a boolean value indicating whether the object is an instance of the corresponding type or not.
if(ep instanceof UserProfile)
{
alert('ep is an instance of type UserProfile');
} else
{
alert('ep is NOT an instance of type UserProfile');
}
You can add this code to the method validateUser
. In our case, it must return true
. Similarly, obj instanceof Object
for any non-null
value for obj
will return true
.
Method Reference and Optimization
And a very crucial item before we get back on to our main agenda.
Depending upon how we write the code, the methods may be loaded in memory multiple times. Which is bad for the application. We do not want identical piece of code to be loaded in memory multiple times. The code must be loaded only once and be executed in the context of the corresponding object. What am I talking about? Update the code for the method validateUser
as follows:
function validateUser()
{
var eid = document.getElementById('i').value;
var uname = document.getElementById('u').value;
var pwd = document.getElementById('p').value;
var ep1 = new EmployeeProfile(uname, pwd, eid);
var ep2 = new EmployeeProfile(uname, pwd, eid);
alert('Are references same? ' + (ep1.authenticate == ep2.authenticate));
}
You are bound to get a false
! It hurts. :(
Everytime we instantiate EmployeeProfile
(or even UserProfile
for that matter), code for the method authenticate
is loaded into memory. Imagine if we have 1000 instances. Not only are the properties loaded into memory 1000 times, but also the code for the method. It's just too bad.
Let's fix this up...
Update the code for UserProfile
to as follows:
function UserProfile(username, password)
{
this.username = username;
this.password = password;
}
UserProfile.prototype.authenticate = function()
{
if(this.username == 'gvaish' && this.password == 'edujini')
{
return true;
}
return false;
}
Notice the use of prototype
property once again. That holds the key! That's probably - The Key property in JavaScript.
Now, execute the test case once again. ep1.authenticate == ep1.authenticate
must return true
in this case! Detailed discussion around prototype
is out of the scope of this article... but will have one some time soon.
Method Overriding
Next, we explore how to override the method authenticate
. Update the definition of EmployeeProfile
as given below:
function EmployeeProfile(username, password, employeeID)
{
this.username = username;
this.password = password;
this.employeeID = employeeID;
}
EmployeeProfile.prototype = new UserProfile();
EmployeeProfile.prototype.authenticate = function()
{
if(this.employeeID == 123 && this.username == 'gvaish'
&& this.password == 'edujini')
{
return true;
}
return false;
}
Go ahead and authenticate with various combinations of the employeeID
, username
and password
. Hurray! The method has been overridden (using the line of hierarchy of the object prototype
).
The last thing that we see in method overriding is how to invoke the base-type method in the sub-type method. Well, the solution again lies in prototype
!
Let us define the business logic for an employee's authentication as follows:
- It checks for the authentication results from
UserProfile
. If it has failed, the result is failure. - It additionally checks for
employeeID
.
This way we keep EmployeeProfile
independent of how the UserProfile
authenticates itself. Modify EmployeeProfile
as follows:
function EmployeeProfile(username, password, employeeID)
{
this.username = username;
this.password = password;
this.employeeID = employeeID;
}
EmployeeProfile.prototype = new UserProfile();
EmployeeProfile.prototype._authenticate = EmployeeProfile.prototype.authenticate;
EmployeeProfile.prototype.authenticate = function()
{
if(this._authenticate())
{
return this.employeeID == 123;
}
return false;
}
Note that this is one of the various possible ways of achieving our target. We create a reference _authenticate
to the original authenticate
method (but did we get it from EmployeeProfile
since we defined it in UserProfile
- that's the magic of prototype
in JavaScript).
Rerun your test case... Wow! We get what we expect...
Summary
In this article, we learnt how encapsulation is implemented in JavaScript. To summarize, we explored the following:
- Inheritance in JavaScript
- Use of
prototype
property to mark inheritance - Defining new methods in the inherited type
- Override existing methods, optionally also calling the original method
instanceof
operator to check the underlying data-type inheritance hierarchy