Object Oriented JavaScript
- 38 minutes read - 8019 wordsJavaScript has been regarded as a functional programing language by most of the developers. This book is targeted for all the JavaScript developers to shift their thinking from the functional to the object oriented programing language.
The course requires basic knowledge of JavaScript syntax. If you already know some JavaScript, you will find a lot of eye-openers as you go along and learn what more the JavaScript can do for you.
This course starts with the basics of JavaScript programming language and the object oriented programing (OOP) concepts and how to achieve the same using JavaScript. The additional JavaScript concepts like AMD, JSON, popular frameworks, and libraries are also included in the document.
This course covers the JavaScript OOP concepts in comparison with the well-known Object Oriented Programing language Java. You will have the comparative code in Java and JavaScript and the use of the famous IDE Eclipse.
The usage of JavaScript has been changed a lot since its invention. Earlier it was used only as the language for the web browser for the client-side scripting, but now it is also being used for server-side scripting, desktop applications, mobile apps, gaming, graphics, charting-reporting, etc. The following diagram depicts its usages:
Programming in JavaScript
Let’s recall the JavaScript language, this section covers the very basics of JavaScript, but it may be an eye opener for some developers, so try the examples given below and learn the flexibility of the JavaScript programming language. I hope you know how to run the example given below, just add these in <script>
tag on a HTML page.
General Statements
Following are different type of statements of JavaScript language.
// Single Line Comment
/**
*
* Multi-Line
* Comment
*/
//Declare
var variable1;
//Declare and define
var variable1 = 10;
//Assign
variable1 = "New value";
//Check type of a variable
alert (typeof variable1); // string - dynamic type language.
console.log ("variable1=" + variable1); //log to console. I hope you know ‘console’, if not //press F12 in chrome or IE and see the tab ‘Console’.
Operators
Equality Checking
==
equal to===
equal value and equal type!=
not equal!==
not equal value or not equal type>
greater than<
less than>=
greater than or equal to<=
less than or equal to
Guard/Default Operator
&&
- and||
- or
The logical operators are and, or, and not. The && and || operators accept two operands and provides their logical result. && and || are short circuit operators. If the result is guaranteed after the evaluation of the first operand, it skips the second operand.
Technically, the exact return value of these two operators is also equal to the final operand that is evaluated. This is why the && operator and the || operator are also known as the guard operator and the default operator respectively.
Array and Map
See the beauty of JavaScript, flexible use of array.
//Array
var arrayVar = [];
arrayVar [0] = "Text";
arrayVar [1] = 10;
//Run time memory allocation.
arrayVar [arrayVar.length] = "Next";
console.log (arrayVar.length); // 3
console.log (arrayVar [0]); // Text
//Map Usage of same Array.
arrayVar ["Name"] = "Kuldeep";
arrayVar ["Group"] = "JSAG";
console.log (arrayVar ["Name"]); // Kuldeep
//You can also access String Key with DOT operator.
console.log (arrayVar.Name); // Kuldeep
//You can also access index as map way.
console.log (arrayVar ["0"]); // Text
console.log (arrayVar.length); //3 – Please note length of array is increase only for numeric values
arrayVar ["3"] = "JSAG";
console.log (arrayVar.length) //4 – Numeric values in String also treated as numeric. Auto cast
arrayVar [500] = "JSAG";
console.log (arrayVar.length) //501
Object Literals
Object literals can be defined using JSON syntax.
var person1 = {
name: "My Name",
age: 30,
cars: [1, 3],
addresses: [{
street1: "",
pin: ""
}, {
street1: "",
pin : ""
} ]
};
console.log (person1.name);
console.log (person1.addresses [0].street1);
Control Statements
If-else-if chain
Same as any other language.
var value;
var message = "";
if (value) {
message = "Some valid value";
if (value == "O") {
message = "Old Value";
} else if (value == "N") {
message = "New Value";
} else {
message = "Other Value";
}
} else if (typeof (value) == "undefined") {
message = "Undefined Value";
} else {
message = value;
}
console.log ("Message : " + message);
Switch
Switch accepts both numeric and string in same block.
var value = "O";
var message = "";
switch (value) {
case 1:
message = "One";
break;
case "O":
message = "Old Value";
break;
case "N":
message = "New Value";
break;
case 2:
message = "Two";
break;
default:
message = "Unknown";
}
console.log ("Message : " + message);
Loop
The while loop, continue, break statements same as Java.
//The while loop
while (value > 5) {
value --;
if (value == 100) {
console.log ("Break Condition");
break;
} else if (value > 10) {
console.log ("Value more than 10");
continue;
}
console.log ("Value : " + value);
}
The for loop
//For loop on array
for (var index = 0; index < arrayVar.length; index++) {
console.log ("For Index - ", index, arrayVar [index]);
// Note that it will only print the array with index numeric indexes (because of ++operation).
}
//For each loop (similar to Java)
for (var key in arrayVar) {
console.log ("For Key - ", key, arrayVar [key]);
// Note that it will print all the values of array/map w.r.t keys.
}
The do-while loop
// Do while loop.
do {
console.log ("Do while");
if (value) {
value = false;
}
} while (value)
Function
Similar to the other programming language a function is a set of statements to perform some functionality.
Define
The functions are defined as follows:
// Declarative way
/**
* Perform the given action
*
* @param {String} actionName - Name of the action
* @param {Object} payload - payload for the action to be performed.
* @return {boolean} result of action
*/
function performAction (actionName, payload) {
console.log ("perform action:” actionName,” payload:” payload);
return true;
}
Since JS is dynamic language, we don’t need to specify the type of parameters and type of return value.
Invoke
We can invoke a function as follows
var returnValue = performAction ("delete", "userData");
console.log (returnValue);
The ‘arguments’ variable
One can access/pass arguments in a function even if they are not defined in the original function. The ‘arguments’ variable is an object of type array-like, you can perform most of the operation of an array on this.
/**
* Perform an action
*
* @return {boolean} result of action
*/
function performAction () {
console.log (arguments.length);
console.log ("perform action:” arguments [0],” payload:” arguments [1]);
return true;
}
var returnValue = performAction ("delete", "userData");
console.log (returnValue);
Function as Object
We can assign a function definition to an object, and access the function using the objects.
/**
* Perform an action
*
* @return {boolean} result of action
*/
var myFunction = function performAction () {
console.log (arguments.length);
console.log ("perform action :” arguments [0],” payload:” arguments [1]);
Return true;
};
console.log (typeof myFunction); //function
var returnValue = myFunction ("delete", "userData");
Function inside Function
In Javascript, you can define functions inside a function.
/**
* Perform an action
*
* @return {boolean} result of action
*/
var myFunction = function performAction () {
console.log (arguments.length);
function doInternalAction (actionName, payload) {
console.log ("perform action:” actionName,” payload:” payload);
return true;
}
return doInternalAction (arguments [0], arguments [1]);
};
Exception Handling
JavaScript supports exception handling similar to other languages.
var obj;
try {
console.log ("Value = " + obj.check);
} catch (e) {
console.log (e.toString ());
console.log ("Error occurred of type : " + e.name);
console.log ("Error Details: " + e.message);
}
TypeError: Cannot read property 'check' of undefined
Error occurred of type: TypeError
Error Details: Cannot read property 'check' of undefined
The Error object has name and message. You can also throw your own errors having name and message.
function check (obj) {
if (! obj.hasOwnProperty ("check")) {
throw {name: "MyCheck", message: "Check property does not exists"};
// Also try... throw new Error (“message”);
}
}
var obj = {}
try {
check (obj);
} catch (e) {
console.log (e.toString ());
console.log ("Error occurred of type: " + e.name);
console.log ("Error Details: " + e.message);
}
[object Object]
Error occurred of type: MyCheck
Error Details: Check property does not exists
Context
this
The context of a function is accessed via “this
” keyword. It is a reference to the object using which the function is invoked. If we call a function directly, “this
” refers to the window
object.
function log (message) {
console.log (this, message);
}
log ("hi");
var Logger = {}
Logger.info = function info (message) {
console.log (this, message);
}
Logger.info ("hi");
Window {top: Window, window: Window, location: Location, external: Object, chrome: Object…}
"hi"
Object {info: function}
"hi"
Invoking functions with context
You can change the context of a function while invoking using “call
” and “apply
” syntax.
[function object].call ([context], arg1, arg2, arg3 …);
[function object].apply ([context], [argument array]);
function info (message) {
console.log (this, "INFO:" + this.name + ":" + this.message +" input: “+ message);
}
var iObj = {
name: "My Name",
message: "This is a message",
};
info.call (iObj, "hi");
info.apply (iObj, ["hi"]);
Object {name: "My Name", message: "This is a message"} "INFO: My Name: This is a message input: hi"
Object {name: "My Name", message: "This is a message"} "INFO: My Name: This is a message input: hi"
Scope
The scope of a variable is limited to the function definition. The variable remains alive till the existence of the function.
function script_js () {
var info = function info (message) {
console.log ("INFO:" + message);
};
var message = “This is a message";
}
info (message);
Uncaught ReferenceError: message is not defined
If a variable is not defined with a var keyword, it is referred as a global variable which is attached to the window
context.
function script_js () {
info = function info (message) {
console.log ("INFO:" + message);
};
message = “This is a message";
}
script_js ();
info (message);
INFO: This is a message
But for accessing such global variables we need to call the methods once to initialize them in the scope. We can limit a variable to a scope w.r.t to a js file, or to a block by defining it inside a function.
The scoping functions need to be called immediately after the definition; this is called IIFE (Immediately Invoked Function Expression). Instead of explicitly calling the scoping functions we can call the function along with its definition as follows.
(function script_js () {
info = function info (message) {
console.log ("INFO:" + message);
};
message = “This is a message";
})();
info (message);
IIFE can be used to limit the scope of the variables w.r.t a file, block, etc. and whichever needs to be exposed outside the scope can be assigned to the global variables.
Reflection
Reflection is examining the structure of a program and its data. The language of a program is called a programming language (PL), and the language in which the examination is done is called a meta programming language (MPL). PL and MPL can be the same language. For example,
function getCarData () {
var myCar = {
name: "Toyota",
model: "Corolla",
power_hp: 1800,
getColor: function () {
// ...
} };
alert (myCar.hasOwnProperty ('constructor')); // false
alert (myCar.hasOwnProperty ('getColor')); // true
alert (myCar.hasOwnProperty ('megaFooBarNonExisting')); // false
if (myCar.hasOwnProperty ('power_hp')) {
alert (typeof (myCar.power_hp)); // number
}
}
getCarData ();
Eval:
The eval ()
method evaluates a JavaScript code represented as a string, for example:
var x = 10;
var y = 20;
var a = eval ("x * y");
var b = eval ("2 + 3");
var c = eval ("x + 11");
alert ('a='+a);
alert ('b='+b);
alert ('c='+c);
####Timers and Intervals With JavaScript, we can execute some code at the specified time-intervals, called timing events. To time events in JavaScript is very easy. The two key methods that are used are:
-
setInterval()
- executes a function, over and over again, at the specified time intervals it wait a specified number of milliseconds, and then execute a specified function, and it will continue to execute the function, once at every given time-interval.window.setInterval ("javascript function", milliseconds); //Alert "hello" every 2 secondss setInterval (function () {alert ("Hello")}, 2000);
-
setTimeout()
- executes a function, once, after waiting a specified number of millisecondswindow.setTimeout ("javascript function", milliseconds);
Object Oriented Programing in JavaScript
As HTML5 standards are getting matured, JavaScript has become the most popular programming language of the web for client-side environments, and is gaining its ground rapidly on the server-side as well. Despite the popularity, major concern of JavaScript is code quality, as JavaScript code-bases grow larger and more complex, the object-oriented capabilities of the language become increasingly crucial. Although JavaScript’s object-oriented capabilities are not as powerful as those of static server-side languages like Java, its current object-oriented model can be utilized to write a better code.
The Class and Object Construction
A class consists of data and operations on those data. A class has construction and initialization mechanisms. Let’s discuss how a class can be defined in JavaScript. There is no special keyword in JavaScript to define a class; a class is a set of following:
-
Constructor - A constructor is defined as a normal function.
/** * This is the constructor. */ function MyClass () { };
Let’s make it better to avoid calling it a function.
/** * This the constructor. */ function MyClass () { if (! (this instanceof MyClass)){ throw new Error ("Constructor can't be called as function"); } }; MyClass ();
Uncaught Error: Constructor can't be called as function
Every object has a prototype associated to it. For the above class, the prototype will be:
MyClass.prototype { constructor: MyClass () { if (! (this instanceof MyClass)){ throw new Error ("Constructor can't be called as function"); } }, __proto__: Object {...} }
-
Instance Fields You can define the instance fields by attaching the variables with “
this
” or class’s prototype, inside the constructor or after creating the object. Class’s prototype can also be defined using JSON Syntax./** * This the constructor. * @constructor */ function MyClass () { if (! (this instanceof MyClass)){ throw new Error ("Constructor can't be called as function"); } this.instanceField1 = "instanceField1"; this.instanceField2 = "instanceField2"; }; MyClass.prototype.name = "test";
Let’s initialize the fields using constructor
/** * This the constructor. * @param {String} arg1 * @param {int} arg2 * @constructor */ function MyClass (arg1, arg2) { if (! (this instanceof MyClass)){ throw new Error ("Constructor can't be called as function"); } this.instanceField1 = arg1; this.instanceField2 = arg2; };
-
Instance Methods - The same way we define instance methods.
/** * This the constructor. * @param {String} arg1 * @param {int} arg2 * @constructor */ function MyClass (arg1, arg2) { if (! (this instanceof MyClass)){ throw new Error ("Constructor can't be called as function"); } this.instanceField1 = arg1; this.instanceField2 = arg2; /** * Print the fields */ this.printFields = function printFields () { console.log ("instanceField1: " + this.instanceField1 + " instanceField2: " + this.instanceField2); }; }; MyClass.prototype.printName = function printName () { console.log (this.name); };
-
Instance Creation - An instance can be created using a new keyword.
var myInstance = new MyClass ("Kuldeep", 31); console.log (myInstance.instanceField1); console.log (myInstance.instanceField2); myInstance.printName (); myInstance.printFields (); var myInstance1 = new MyClass ("Sanjay", 30); console.log (myInstance1.instanceField1); console.log (myInstance1.instanceField2); myInstance1.printFields (); myInstance1.printName ();
Kuldeep 31 test instanceField1: Kuldeep instanceField2: 31 Sanjay 30 instanceField1: Sanjay instanceField2: 30 test
You can add/update/delete instance fields from the instance variable as well, any change in an instance variable will only be effective in that instance variable.
myInstance.instanceField1 = "Kuldeep Singh"; myInstance1.instanceField3 = "Rajasthan"; myInstance1.name = "New test"; MyClass.prototype.name = 100; myInstance.printFields = function () { console.log ("1: " + this.instanceField1 + " 2: " + this.instanceField2 +" 3: “+ this.instanceField3); }; myInstance.printFields (); myInstance1.printFields (); myInstance.printFields.call (myInstance1); myInstance1.printName ();
1: Kuldeep Singh 2: 31 3: undefined script.js:227 instanceField1: Sanjay instanceField2: 30 script.js:198 1: Sanjay 2: 30 3: Rajasthan script.js:227 New test
Please note that once an object is created, any change in the prototype will not impact the created instance. On “
new
” a copy of class’s prototype is created and assigned to instance variable. Constructors then initiate/update the members copied from prototype. -
Static Fields and Methods - The static fields are variables which are associated with a class, not with an instance. In JavaScript the static variables are only accessible to the class reference, unlike Java they are not accessible using an instance variable. There is no static keyword in JavaScript.
MyClass.staticField1 = 100; MyClass.staticField2 = "Static"; MyClass.printStaticFields = function () { console.log ("staticField1: " + this.staticField2 + " staticField2: " + this.staticField2); }; var myInstance = new MyClass ("Kuldeep", 31); MyClass.printStaticFields (); MyClass.printOtherFields = function () { console.log ("1: " + this.instanceField1 + " 2: " + this.instanceField2); }; MyClass.printOtherFields (); myInstance.printStaticFields ();
staticField1: Static staticField2: Static 1: undefined 2: undefined Uncaught TypeError: Object #<MyClass> has no method 'printStaticFields'
-
Singleton - If you need to use only one instance of an object, use a Singleton either by defining the class by the static methods or define the object as literals using JSON syntax.
Encapsulation
In the above example we have clubbed the data and the operations in one class structure but the data can directly be accessed outside of the class as well. So it is a violation of the encapsulation concept. In this section we will see how we can protect the data inside a class or in other words define the access rules for the data and the operations. In JavaScript there is no access modifier such as public, private or protected or package. But we have discussed the scope; by function scoping we can limit the access to the data. Going forward let’s write all the code in IIFE scoping function to control the file level access of the variables.
-
Public - The variables and the methods which are associated with “
this
” orClass.prototype
is by default public.person.js
(function () { /** * This the constructor. * * @param {String} name * @param {int} age * @constructor */ function Person (name, age) { if (! (this instanceof Person)) { throw new Error ("Constructor can't be called as function"); } /** * @public Name */ this.name = name; /** * @public Age */ this.age = age; /** * @public * @return String representation */ this.toString = function toString () { return "Person [name:" + this.name + ", age:" + this.age + "]"; }; }; /** * @public Full Name of the person. */ Person.prototype.fullName = "Kuldeep Singh"; var person = new Person ("Kuldeep", 31); console.log ("Person: " + person); console.log (person.name); console.log (person.fullName); })();
Person: Person [name: Kuldeep, age: 31] Kuldeep Kuldeep Singh
-
Private - Anything defined with ‘
var
’ inside a function remains private to that function. So variables and methods defined with var keywords in a constructor are private to the constructor and logically with the class and also the declarative style inner methods are in the private scopes.(function () { /** * This the constructor. * * @param {String} name * @param {int} age * @constructor */ function Person (name, age) { if (! (this instanceof Person)) { throw new Error ("Constructor can't be called as function"); } /** * @private Name */ var _name = name; /** * @private Age */ var _age = age; /** * @public String representation * @return */ this.toString = function toString () { return "Person [name:" + _name + ", age:" + _age + "]"; }; }; var person = new Person ("Kuldeep", 31); console.log ("Person: " + person); console.log (person._name); })();
Person: Person [name: Kuldeep, age: 31] undefined
Expose the data using getters and setters wherever required, accessing private members is called privileged access. The private members are not assessable from a prototypical definition (
Class.prototype
) of the public methods.(function () { /** * This is the constructor. * * @param {String} name * @param {int} age * @constructor */ function Person (name, age) { if (! (this instanceof Person)) { throw new Error ("Constructor can't be called as function"); } /** * @private Name */ var _name = name; /** * @private Age */ var _age = age; /** * @public */ this.getName = function getName () { return _name; }; /** * @public */ this.getAge = function getAge () { return _age; }; /** * @public String representation * @return */ this.toString = function toString () { return "Person [name:" + _name + ", age:" + _age + "]"; }; }; var person = new Person ("Kuldeep Singh", 31); console.log (person.getAge ()); console.log (person.getName ()); })();
It’s a good practice to define public API in the prototype of a class and access private variables using the privileged methods.
Inheritance
In JavaScript there are two ways to implement inheritance:
-
Prototypical Inheritance - This type of inheritance is implemented by
- Copying the prototype of the base class to the sub class,
- Changing the constructor property of the prototype
We have created a person class. Now let’s expose the class with the
window
context. person.js(function () { /** * This is the constructor. * * @param {String} name * @param {int} age * @constructor */ function Person (name, age) { if (! (this instanceof Person)) { throw new Error ("Constructor can't be called as function"); } var _name = name; var _age = age; this.getName = function getName (){ return _name; }; this.getAge = function getAge () { return _age; }; /** * @public String representation * @return */ this.toStr = function toString () { return "name :" + _name + ", age :" + _age; }; }; window.Person = Person; })();
Let’s extend the person class and create an Employee class.
Employee.prototype = new Person (); Employee.prototype.constructor = Employee;
JavaScript does not have any “
super
” keyword so we need to call super constructor explicitly. Using call or apply. employee.js(function () { /** * This is the constructor. * * @param {String} type * @constructor */ function Employee (code, firstName, lastName, age) { if (! (this instanceof Employee)) { throw new Error ("Constructor can't be called as function"); } //Calling super constructor Person.call (this, firstName, age); // Private fields var _code = code; var _firstName = firstName; var _lastName = lastName; // Public getters this.getCode = function getCode () { return _code; }; this.getFirstName = function getFirstName () { return _firstName; }; this.getLastName = function getLastName () { return _lastName; }; this.toStr = function toString () { return "Code:" + _code + ", Name :" + _firstName +" "+ _lastName; }; }; Employee.prototype = new Person (); Employee.prototype.constructor = Employee; var emp = new Employee (425, "Kuldeep", "Singh", 31); console.log ("Employee: " + emp.toStr ()); console.log (emp.getAge ()); console.log (emp.getName ()); })();
Employee: Employee [Code: 425, Name: Kuldeep Singh] 31 Kuldeep
All the methods of the Person class are available in the Employee class. You can also check
instanceof
andisPrototypeOf
.console.log (emp instanceof Employee); console.log (emp instanceof Person); console.log (Employee.prototype.isPrototypeOf (emp)); console.log (Person.prototype.isPrototypeOf (emp));
The complete prototype chain is maintained; when a member of a child object is accessed, it is searched from the members of the object. If it is not found there, it is searched in its
prototype
object. If it is not found there, it is searched in the members and theprototype
of the parent object. It is searched up to the prototype of the object.The prototypical inheritance supports only the multi-level inheritance. If you make a change in the base classes/its prototype after creating the object instance, the change will be effecting as per the traversal rule explained.
In the above example, if you add a method in the Person class after creating emp object, that method will also be available in emp object even if it was not there when the emp object was created.
In above code we have also overridden
toStr
method and called thesuper
constructor from the constructor.Improvement:
We have created a new object of Person to create a copy of its prototype. Here we have done an extra operation to call the constructor as well with no arguments. We can avoid this extra operation by following.
Instead of
new Person(),
doObject.create (Person.prototype)
It creates a better prototype chain for the created object.
_With
new
Employee.prototype = new Person (); Employee.prototype.constructor = Employee;
With
Object.create()
Employee.prototype = Object.create (Person.prototype); Employee.prototype.constructor = Employee;
-
Mixin Based Inheritance - When we need to inherit the functionality of the multiple objects, we use this approach to implement inheritance. This can be achieved by the external libraries which copy all the properties of the source objects to the destination objects, such as
_.extends
or$.extends
Let’s try with jQuery, include “jquery-x.y.z.min.js
” in html page. Change the Person class as a JSON object:person.js
(function () { Person = { constructor: function Person (name, age) { var _name = name; /** * @private Age */ var _age = age; /** * @public */ this.getName = function getName () { return _name; }; /** * @public */ this.getAge = function getAge () { return _age; }; /** * @public String representation * @return */ this.toStr = function toString () { return "name:" + _name + ", age:" + _age; }; } }; window.Person = Person; })();
Similarly, Employee class as a JSON object:
employee.js
(function () { Employee = { constructor: function Employee (code, firstName, lastName, age) { // Call super Person.constructor.call (this, firstName, age); // Private fields var _code = code; var _firstName = firstName; var _lastName = lastName; // Public getters this.getCode = function getCode () { return _code; }; this.getFirstName = function getFirstName () { return _firstName; }; this.getLastName = function getLastName () { return _lastName; }; this.toStr = function toString () { return "Code :" + _code + ", Name :" + _firstName + " " + _lastName; ; }; } }; var emp = {}; $.extend (emp, Person, Employee).constructor (425, "Kuldeep", "Singh", 31); console.log (emp); var emp2 = {}; $.extend (emp2, Person, Employee).constructor (425, "Test1", "Test1", 30); console.log ("Employee : " + emp.toStr ()); console.log (emp.getAge ()); console.log (emp2.getName ()); })();
Employee {constructor: function, getName: function, getAge: function, toStr: function, getCode: function…} Employee: Code: 425, Name: Kuldeep Singh 31 Test1
With this approach you cannot check
instanceof
orisPrototypeOf
console.log (emp instanceof Employee);
console.log (emp instanceof Person);
console.log (Employee.prototype.isPrototypeOf (emp)); console.log (Person.prototype.isPrototypeOf (emp));Because all the properties are copied in the object and they remain as its own property.
Please be informed that if you make changes in the structure (adding/changing/deleting property) of the original base classes after creating the object then that change will not be propagated to the instance of the child object. So, the mixing-based inheritance does not keep the parent-child relationship.
Abstraction
Due to the flexibility of JavaScript, abstraction is not much needed, as we can pass/use the data without defining their types. But, yes we can implement the abstract types and interfaces to enforce some common functionality.
-
Abstract Type - The abstract types are the data types (classes) which cannot be instantiated. Such data types are used to contain common functionality as well as abstraction of the functionality that a sub-class should implement. For example, the human class - human.js
(function () { function Human () { if (! (this instanceof Human)) { throw new Error ("Constructor can't be called as function"); } throw new Error ("Can’t instantiate Human class"); }; Human.prototype.getClassName = function _getClassName () { return "class: Human"; }; /** * @abstract */ Human.prototype.getBirthDate = function _getBirthDate () { throw new Error ("Not implemented"); }; window.Human = Human; })(); var h1 = new Human ();
Uncaught Error: Can’t instantiate Human class Let’s extend this class in Person.
person.js
(function () { /** * This is the constructor. * * @param {String} name * @param {int} age * @constructor */ function Person (name, age) { if (! (this instanceof Person)) { throw new Error ("Constructor can't be called as function"); } var _name = name; var _age = age; this.getName = function getName () { return _name; }; this.getAge = function getAge () { return _age; }; /** * @public String representation * @return */ this.toStr = function toString () { return "name:" + _name + ", age:" + _age; }; }; Person.prototype = Object.create (Human.prototype); Person.prototype.constructor = Person; window.Person = Person; var p1 = new Person ("Kuldeep", 31); console.log (p1.getName ()); console.log (p1.getClassName ()); console.log (p1.getBirthDate ()); })();
Kuldeep class: Human Uncaught Error: Not implemented
So implementation needs to be provided for the abstract methods.
-
Interface - Interface is similar to an abstract class but it enforces that implementing class must implement all the methods declared in the interface. There is no direct interface or a virtual class in JavaScript, but this can be implemented by checking whether a method is implemented in the class or not. Example: human.js
(function () { var IFactory = function (name, methods) { this.name = name; // copies array this.methods = methods.slice (0); }; /** * @static * Method to validate if methods of interface are available in 'this_obj' * * @param {object} this_obj - object of implementing class. * @param {object} interface - object of interface. */ IFactory.validate = function (this_obj, interface) { for (var i = 0; i < interface.methods.length; i++) { var method = interface.methods[i]; if (! this_obj [method] || typeof this_obj [method]! == "function") { throw new Error ("Class must implement Method: " + method); } } }; window.IFactory = IFactory; /** * @interface */ var Human = new IFactory("Human", ["getClassName", "getBirthDate"]); window.Human = Human; })();
Let’s have a class which must comply with the Human interface definition. person.js
(function () { /** * This is the constructor. * * @param {String} name * @param {int} age * @constructor */ function Person (name, age) { if (! (this instanceof Person)) { throw new Error ("Constructor can't be called as function"); } var _name = name; var _age = age; this.getName = function getName () { return _name; }; this.getAge = function getAge () { return _age; }; /** * @public String representation * @return */ this.toStr = function toString () { return "name:" + _name + ", age:" + _age; }; IFactory.validate (this, Human); }; window.Person = Person; var p1 = new Person ("Kuldeep", 31); console.log (p1.getName ()); })();
Uncaught Error: Class must implement Method: getClassName Let’s implement the unimplemented methods:
person.js
(function () { /** * This is the constructor. * * @param {String} name * @param {int} age * @constructor */ function Person (name, age, dob) { if (! (this instanceof Person)) { throw new Error ("Constructor can't be called as function"); } var _name = name; var _age = age; var _dob = dob; this.getName = function _getName () { return _name; }; this.getAge = function _getAge () { return _age; }; this.getBirthDate = function _getDob () { return _dob; }; /** * @public String representation * @return */ this.toStr = function toString () { return "name:" + _name + ", age:" + _age; }; IFactory.validate (this, Human); }; Person.prototype.getClassName = function _getClassName () { return "class: Person"; }; window.Person = Person; var p1 = new Person ("Kuldeep", 31, "05-Aug-1982"); console.log (p1.getName ()); console.log (p1.getBirthDate ()); console.log (p1.getClassName ()); })();
Kuldeep 05-Aug-1982 class: Person
Polymorphism
Polymorphism is referred to as multiple forms associated with one name. In JavaScript polymorphism is supported by overloading and overriding the data and the functionality.
-
Overriding - Overriding is supported in JavaScript, if you define the function/variable with same name again in child class it will override the definition which was inherited from the parent class.
- Function overriding - The example of overriding can be found in Person-Employee example of Inheritance
- Variable overriding - You can override the variables in the same way as you can override the functions.
- Calling
super.function()
- Now let’s see how we can inherit the functionality of the base class while overriding, in other words, callingsuper.method()
. Since there is no “super
” keyword in JavaScript, we need to implement it using call/apply.
Let’s consider person.js created in the last section. person.js
(function () { /** * This is the constructor. * * @param {String} name * @param {int} age * @constructor */ function Person (name, age, dob) { if (! (this instanceof Person)) { throw new Error ("Constructor can't be called as function"); } var _name = name; var _age = age; var _dob = dob; this.getName = function _getName () { return _name; }; this.getAge = function _getAge () { return _age; }; this.getBirthDate = function _getDob () { return _dob; }; /** * @public String representation * @return */ this.toStr = function toString () { return "name:" + _name + ", age:" + _age; }; IFactory.validate (this, Human); }; Person.prototype.getClassName = function _getClassName () { return "class: Person"; }; window.Person = Person; })();
And have employee.js as follows: employee.js
(function () { /** * This is the constructor. * * @param {String} type * @constructor */ function Employee (code, firstName, lastName, age) { if (! (this instanceof Employee)) { throw new Error ("Constructor can't be called as function"); } //Calling super constructor Person.call (this, firstName, age); // Private fields var _code = code; var _firstName = firstName; var _lastName = lastName; // Public getters this.getCode = function _getCode () { return _code; }; this.getFirstName = function _getFirstName () { return _firstName; }; this.getLastName = function _getLastName () { return _lastName; }; this.toStr = function _toString () { return "Code:" + _code + ", Name :" + _firstName +" "+ _lastName; }; }; Employee.prototype = Object.create (Person.prototype); Employee.prototype.constructor = Employee; Employee.prototype.getClassName = function _getClassName () { //Calling super.getClassName () var baseClassName = Person.prototype.getClassName.call (this); return "class: Employee extending " + baseClassName; }; var emp = new Employee (425, "Kuldeep", "Singh", 31); console.log ("Employee: " + emp.toStr ()); console.log (emp.getClassName ()); })();
Employee: Code: 425, Name: Kuldeep Singh class: Employee extending class: Person
-
Privileged Function vs. Public Function - Now let’s try to call the privileged function in the child class. Change the Emplyee.toStr method as follows
this.toStr = function toString () { var baseStr = Person.prototype.toStr.call (this); return "Code:" + _code + ", Name:" + _firstName +" "+ _lastName + " baseStr:" + baseStr; };
And you will get Uncaught TypeError: Cannot call method ‘call’ of undefined The reason is you cannot call the privileged method in the child class, but you can override the privileged method. Please note this deference between the privileged (not linked with the prototype) and the public methods (linked with prototype).
-
Overloading
- Operator Overloading - JavaScript have implement operator overloading inbuilt for “+” operator. For numeric data type it acts as add operation but for String data type it act as concatenation operator. No custom overloading is support for operators.
- Functional Overloading - Since the JavaScript functions are flexible in terms of the number of arguments, the type of arguments, and the return types. Only the method name is considered as the signature of the method. You can pass any number of arguments, or any type of argument to a function, it does not matter if the function definition has included those parameters or not. Please ref to Section JavaScript Functions Due to this flexibility, functional overloading is not supported in JavaScript. If you define another function having same name but different parameters then that another function will just overwrite the original function.
Modularization
Once we have many custom object types, the class and the files, we need to group them and manage their visibility, dependencies, and loading. As of now JavaScript does not directly support the management of the visibility, the dependencies and the loading of the objects or the classes. Generally these things are managed by a namespace/package or modules in the other language. However, future version of JavaScript (ECMAScript 6) will come up with the full-fledged support for a module and a namespace. Till then let’s see how it can be implemented in JavaScript.
-
Package or Namespace - There is no inbuilt
package
or namespace inJavaScript
, but we can implement it JSON style objects. namespaces.js(function () { // create namespace /** * @namespace com.tk.training.oojs */ var com = { tk: { training: { oojs: {} } } }; /** * @namespace com.tk.training.oojs.interface */ com.tk.training.oojs.interface = {}; /** * @namespace com.tk.training.oojs.manager */ com.tk.training.oojs.manager = {}; //Register namespace with global. window.com = com; })();
Let’s use these namespaces in the classes we have created so far. human.js
(function () { ... ... /** * @interface */ var Human = new IFactory ("Human", ["getClassName", "getBirthDate"]); //register with namespace com.tk.training.oojs.interface.IFactory = IFactory; com.tk.training.oojs.interface.Human = Human; })(); // person.js (function () { function Person (name, age, dob) { ... ... com.tk.training.oojs.interface.IFactory.validate (this, com.tk.training.oojs.interface.Human); }; Person.prototype.getClassName = function _getClassName () { return "class: Person"; }; //Register with namespace. com.tk.training.oojs.manager.Person = Person; })();
employee.js
(function () { function Employee (code, firstName, lastName, age) { ... //Calling super constructor com.tk.training.oojs.manager.Person.call(this, firstName, age); ... }; Employee.prototype = Object.create (com.tk.training.oojs.manager.Person.prototype); Employee.prototype.constructor = Employee; Employee.prototype.getClassName = function _getClassName () { //Calling super.getClassName () var baseClassName = com.tk.training.oojs.manager.Person.prototype.getClassName.call(this); return "class: Employee extending " + baseClassName; }; com.tk.training.oojs.manager.Employee = Employee; })(); var emp = new com.tk.training.oojs.manager.Employee(425, "Kuldeep", "Singh", 31); console.log (emp); console.log (emp.getClassName ())
This approach is quite verbose. You can shorthand by creating local variable for package such as
var manager = com.tk.training.oojs.manager console.log (manger.Person)
-
Modules - A module is a way of dividing a JavaScript into packages, with that we define what a module is exporting and what are its dependencies.
Let’s define a simple module:
/** * @param {Array} dependencies * @param {Array} parameters */ function module (dependencies, parameters) { var localVariable1 = parameters [0]; var localVariable2 = parameters [1]; /** * @private function */ function localFunction () { return localVariable1 = localVariable1 + localVariable2; } /** * @exports */ return { getVariable1: function _getV1 () { return localVariable1; }, getVariable2: function _getV2 () { return localVariable2; }, add: localFunction, print: function _print () { console.log ("1=", localVariable1, “2=", localVariable2); } }; } var moduleInstance = module ([], [5, 6]); console.log (moduleInstance.getVariable1 ()); console.log (moduleInstance.getVariable2 ()); console.log (moduleInstance.add ()); moduleInstance.print (); moduleInstance = module ([], [11, 12]); console.log (moduleInstance.getVariable1 ()); console.log (moduleInstance.getVariable2 ()); console.log (moduleInstance.add ()); moduleInstance.print ();
5 6 11 1= 11, 2= 6 11 12 23 1= 23, 2= 12
A module encapsulates the internal data and exports the functionality, we can create a module for creating the objects for the classes. Every time a module is called, it returns a new instance of the returning JSON object. All the exported properties and the APIs are accessed from the returned object. Let’s change the classes created so far to follow the module pattern.
namespaces.js
var com = (function () { // create namespace /** * @namespace com.tk.training.oojs */ var com = { tk: {training: {oojs: {}}}}; /** * @namespace com.tk.training.oojs.interface */ com.tk.training.oojs.interface = {}; /** * @namespace com.tk.training.oojs.manager */ com.tk.training.oojs.manager = {}; return com; })();
ifactory.js
com.tk.training.oojs.interface.IFactory = (function () { /** * @static * Method to validate if methods of interface are available in 'this_obj' * * @param {object} this_obj - object of implementing class. * @param {object} interface - object of interface. */ function validate (this_obj, interface) { for (var i = 0; i < interface.methods.length; i++) { var method = interface.methods[i]; if (! this_obj [method] || typeof this_obj [method]! == "function") { throw new Error ("Class must implement Method: " + method); } } }; return {validate: validate}; })();
human.js
com.tk.training.oojs.interface.Human = (function() { return {name: "Human”, methods: ["getClassName", "getBirthDate"]}; })();
person.js
com.tk.training.oojs.manager.Person = (function (dependencies) { //dependencies var IFactory = dependencies [0]; var Human = dependencies [1]; function Person (name, age, dob) { if (! (this instanceof Person)) { throw new Error ("Constructor can't be called as function"); } var _name = name; var _age = age; var _dob = dob; this.getName = function _getName () { return _name; }; this.getAge = function _getAge () { return _age; }; this.getBirthDate = function _getDob () { return _dob; }; /** * @public String representation * @return */ this.toStr = function toString () { return "name:" + _name + ", age:" + _age; }; IFactory.validate (this, Human); }; Person.prototype.getClassName = function _getClassName () { return "class: Person"; }; //export the person class; return Person; }) ([com.tk.training.oojs.interface.IFactory, com.tk.training.oojs.interface.Human]);
employee.js
com.tk.training.oojs.manager.Employee = (function (dependencies) { //dependencies var Person = dependencies [0]; /** * This is the constructor. * * @param {String} type * @constructor */ function Employee (code, firstName, lastName, age) { if (! (this instanceof Employee)) { throw new Error ("Constructor can't be called as function"); } //Calling super constructor Person.call (this, firstName, age); // Private fields var _code = code; var _firstName = firstName; var _lastName = lastName; // Public getters this.getCode = function _getCode () { return _code; }; this.getFirstName = function _getFirstName () { return _firstName; }; this.getLastName = function _getLastName () { return _lastName; }; this.toStr = function toString () { return "Code:" + _code + ", Name:" + _firstName +" "+ _lastName; }; }; Employee.prototype = Object.create (Person.prototype); Employee.prototype.constructor = Employee; Employee.prototype.getClassName = function _getClassName () { //Calling super.getClassName () var baseClassName = Person.prototype.getClassName.call (this); return "class: Employee extending " + baseClassName; }; //export the employee class; return Employee; }) ([com.tk.training.oojs.manager.Person]); var emp = new com.tk.training.oojs.manager.Employee (425, "Kuldeep", "Singh", 31); console.log (emp); console.log (emp.getClassName ());
You can also encapsulate the data in a module, so instead of having a lot of classes we may have modules returning the exposed APIs.
By the above approach we can clearly state the dependencies in each module. But we still need to define a global variable to hold the dependencies. Also, in an HTML page we need to include javascript files in the specific order such that all the dependencies get resolved correctly.
<script src="resources/jquery-1.9.0.min.js"></script> <script src="resources/namespaces.js"></script> <script src="resources/ifactory.js"></script> <script src="resources/human.js"></script> <script src="resources/person.js"></script> <script src="resources/employee.js"></script>
As the number of classes, modules, and files increases, it will be difficult to remember the order of dependencies. So maintaining such a code is still difficult. In next section we will discuss how to resolve this issue.
-
Dependencies Management - The management of dependency in JavaScript can be done using the AMD (Asynchronous Module Definition) approach, which is based upon the module pattern discussed in the last section. The modules are loaded as and when required and only once. In general, the Java Scripts are loaded in the sequence in which they are included in the HTML page, but using AMD they will be loaded asynchronously and in the order they are used.
There are JavaScript libraries which support AMD approach, one of which is
require.js
. We can also group modules in the multiple folders which eliminate the need of namespaces. See the following updated code using AMD approach. Downloadrequire.js
from here, and include it in HTML, remove the other included script tags.<script data-main=" resources/main.js" src="resources/require.js"></script>
We have refactored javascript code in following structure
The syntax for defining a module
define (<array of dependencies>, function (Dependency1, Dependency1 …) { });
The similar syntax is used for require. Let’s define the modules created so far in AMD syntax.
human.js
define (function () { console.log ("Loaded Hunam"); return {name: "Human”, methods: ["getClassName", "getBirthDate"]}; });
ifactory.js
define (function () { console.log ("Loaded IFactory"); /** * @static * Method to validate if methods of interface are available in 'this_obj' * * @param {object} this_obj - object of implementing class. * @param {object} interface - object of interface. */ function validate (this_obj, interface) { for (var i = 0; i < interface.methods.length; i++) { var method = interface.methods[i]; if (! this_obj [method] || typeof this_obj [method]! == "function") { throw new Error ("Class must implement Method: " + method); } } }; return {validate: validate}; });
person.js
define (["interface/human", "interface/ifactory"], function (Human, IFactory) { console.log ("Loaded Person with dependencies: “, Human, IFactory); function Person (name, age, dob) { if (! (this instanceof Person)) { throw new Error ("Constructor can't be called as function"); } var _name = name; var _age = age; var _dob = dob; this.getName = function _getName () { return _name; }; this.getAge = function _getAge () { return _age; }; this.getBirthDate = function _getDob () { return _dob; }; /** * @public String representation * @return */ this.toStr = function toString () { return "name:" + _name + ", age:" + _age; }; IFactory.validate (this, Human); }; Person.prototype.getClassName = function _getClassName () { return "class: Person"; }; //export the person class; return Person; });
employee.js
define (["impl/person"], function (Person) { console.log ("Loaded Employee"); function Employee (code, firstName, lastName, age) { if (! (this instanceof Employee)) { throw new Error ("Constructor can't be called as function"); } //Calling super constructor Person.call (this, firstName, age); // Private fields var _code = code; var _firstName = firstName; var _lastName = lastName; // Public getters this.getCode = function _getCode () { return _code; }; this.getFirstName = function _getFirstName () { return _firstName; }; this.getLastName = function _getLastName () { return _lastName; }; this.toStr = function toString () { return "Code:" + _code + ", Name:" + _firstName +" "+ _lastName; }; }; Employee.prototype = Object.create (Person.prototype); Employee.prototype.constructor = Employee; Employee.prototype.getClassName = function _getClassName () { //Calling super.getClassName () var baseClassName = Person.prototype.getClassName.call (this); return "class: Employee extending " + baseClassName; }; //export the employee class; return Employee; });
main.js
require (["impl/employee"], function (Employee) { console.log ("Loaded Main"); var emp = new Employee (425, "Kuldeep", "Singh", 31); console.log (emp); console.log (emp.getClassName ()); });
Loaded Hunam Loaded IFactory Loaded Person with dependencies: Object {name: "Human", methods: Array [2]} Object {validate: function} Loaded Employee Loaded Main Employee {getName: function, getAge: function, getBirthDate: function, toStr: function, getCode: function…} class: Employee extending class: Person
Also, see the snap of JS loading sequence given below, the timelines human.js and ifactory.js are loaded almost in parallel.
Documentation
This section describes JavaScript documentation using JSDoc, which is a markup language used to annotate the JavaScript source code files. The syntax and the semantics of JSDoc are similar to those of the Javadoc scheme, which is used for documenting code written in Java. JSDoc differs from Javadoc, in that it is specialized to handle the dynamic behavior of JavaScript. Jsdoc_toolkit-2.4.0
Generate Documentation
-
Download the JSDoc toolkit and unzip the archive.
-
Running JsDoc Toolkit requires you to have Java installed on your computer. For more information see http://www.java.com/getjava/.
-
Before running the JsDoc Toolkit app you should change your current working directory to the jsdoc-toolkit folder. Then follow the examples given below or as shown on the project wiki.
-
Command Line - On a computer running Windows a valid command line to run JsDoc Toolkit might look like this:
java -jar jsrun.jar app\run.js -a -t=templates\jsdoc mycode.js
You can use –d switch to specify an output directory.
-
Reports - It generates similar reports as Java Docs
Deployment
We have discussed modularization and documentation, which result in a lot of files and a lot of lines for documentation. There will be lot of round trips on server for loading the JavaScript which is not recommended in production. We should minify, obfuscate and concatenate the JavaScript files to reduce the round trips and size of the JavaScript code to be executed in browsers.
During development we should use the AMD approach, and for production we can optimize the scripts by the following steps.
Install r.js Optimizer
- Download
node.js
and install it. - On node console run following
C:\>node
npm -g require.js
Generate build profile
resources/build-profile.js
({
baseUrl: ".",
name: "main",
out: "main-build.js"
})
Build, Package and Optimize
Run following command in cmd prompt
r.js.cmd -o build-profile.js
It will generate a file main-build.js
as follows, which is a minified, obfuscated and concatenated file.
define("interface/human",[],function(){return console.log("Loaded Hunam"),{name:"Human",methods:["getClassName","getBirthDate"]}}),define("interface/ifactory",[],function(){function e(e,t){for(var n=0;n<t.methods.length;n++){var r=t.methods[n];if(!e[r]||typeof e[r]!="function")throw new Error("Class must implement Method: "+r)}}return console.log("Loaded IFactory"),{validate:e}}),define("impl/person",["interface/human","interface/ifactory"],function(e,t){function n(r,i,s){if(!(this instanceof n))throw new Error("Constructor can't be called as function");var o=r,u=i,a=s;this.getName=function(){return o},this.getAge=function(){return u},this.getBirthDate=function(){return a},this.toStr=function f(){return “name :"+o+", age :"+u},t.validate(this,e)}return console.log("Loaded Person with dependencies : ",e,t),n.prototype.getClassName=function(){return “class: Person"},n}),define("impl/employee",["impl/person"],function(e){function t(n,r,i,s){if(!(this instanceof t))throw new Error("Constructor can't be called as function");e.call(this,r,s);var o=n,u=r,a=i;this.getCode=function(){return o},this.getFirstName=function(){return u},this.getLastName=function(){return a},this.toStr=function f(){return “Code:"+o+", Name :"+u+" "+a}}return console.log("Loaded Employee"),t.prototype=Object.create(e.prototype),t.prototype.constructor=t,t.prototype.getClassName=function(){var n=e.prototype.getClassName.call(this);return “class: Employee extending "+n},t}),require(["impl/employee"],function(e){console.log("Loaded Main");var t=new e(425,"Kuldeep","Singh",31);console.log(t),console.log(t.getClassName())}),define("main",function(){});
Include
Include the optimized script for production release.
<script data-main="resources/main-build.js" src="resources/require.js"></script>
This completes the course. Happy reading!
#oops #object oriented #javascript #js #course #practice #language #technology