Object-Oriented JavaScript (OOJS)



index
Disabled back button Next Section
printable version

Section 1: Defined

The basic idea of OOP is that we use objects to model real world things that we want to represent inside our programs, and/or provide a simple way to access functionality that would otherwise be hard or impossible to make use of.

Objects can contain related data and code, which represent information about the thing you are trying to model, and functionality or behavior that you want it to have. Object data (and often, functions too) can be stored neatly (the official word is encapsulated) inside an object package (which can be given a specific name to refer to, which is sometimes called a namespace), making it easy to structure and access; objects are also commonly used as data stores that can be easily sent across the network.

from Object-oriented JavaScript for beginners

Objects are everywhere in JavaScript – almost every element is an Object whether it is a function, array or string.

from Introduction to Object Oriented Programming in JavaScript

Object Oriented Programming is a programming language paradigm based on the concept that procedures and the data on which they operate can be viewed as two parts of the same thing – an object.

Object-oriented designs are better able to more accurately model the real world than procedural languages.

The fundamental idea behind object-oriented languages is to combine both data and the functions that operate on that data into a single unit called an object.

If implemented correctly:

Both data encapsulation and data hiding are key concepts in the object-oriented paradigm.

The restriction that an object's data can only be changed by the object's methods ensures that nothing else can alter that data, and therefore simplifies writing, debugging, and maintaining the program.

Section 2: Concepts of the Object-Oriented Paradigm

Objects

An object is a computer representation of some real-world thing or event.

Example: if you own a Jeep, you might know several of its characteristics, such as the name of the model (Jeep Wrangler), the vehicle ID number (#51Y62BG826341Y), and the motor type (3.6-liter V-6).

Objects have both attributes (such as the model, VIN, and motor type) and behaviors (such as "lights go on" and "lights go off").


Classes

A class is a category of similar objects.

A class defines the set of shared attributes and behaviors found in each object in the class.

Objects are instances, or specific examples, of classes.

The data components of a class are called instance variables.

Classes must be defined by the programmer. When the program runs, objects can be created from the established class.

Just as an instance of a primitive type is called a variable, an instance of a class is called an object.

Placing data and functions together into a single entity is the central idea of object-oriented programming.

Class figure.
Section 3: Declaration

The ES2015/ES6 standard of JavaScript introduced the class syntax, providing a much more concise way of class declaration using a syntax that is similar to other object-oriented programming languages.


Class Declaration

In JavaScript, we declare the creation of a new class using the class keyword (must be lowercase) followed by a capitalized class name and a pair of curly braces as shown:

class Student {
}


Class Attributes

Note that JavaScript does not require that the class declaration explicitly states which attributes/fields it has, although it is common to do.

Class attributes are simply listed in the class declaration:

class Student {
name;
studentID;

}

Class attributes (a.k.a. class fields or instance variables) are public by default, which violates OO expectations.


Private Class Attributes

ES2020 introduced private variables for classes, to protect class data from exposure beyond the class.

Attributes preceded by a hashtag (#) are private variables.

A key feature of object-oriented programming is data hiding, which means that data is concealed within a class, so that it cannot be accessed mistakenly by functions outside the class.

The primary mechanism for hiding data is to put it in a class and make it private using the hashtag.

Variables or methods that are declared private are accessible only to members of the class.

class Student {
#name;
#studentID;
}


Constructor

constructor(param1, param2, ...) is a special method in the body of a class.

A constructor is vital to the class creation process because it initializes the state of an object (through its attributes) and it is called automatically when a new object is instantiated (defined and created).

Another thing to note is that JavaScript enforces the constructor's name – has to be named constructor().

class Student {
#name;
#studentID;
constructor(sname, sID){
this.#name = sname;
this.#studentID = sID;
}
}


The this Keyword

When using this inside of methods of an ES6 class, it points to the current object.

The this keyword is used to refer to this instance within a class, once it is instantiated.


Accessor & Mutator Methods (Getters & Setters)

If implemented correctly, object-oriented programming limits access to data in a class by using a concept called data hiding.

Here is a tutorial on accessor and mutator methods.

Using get and set methods may seem essentially the same as declaring instance variables as public.

  • However, if an instance variable is public, it may be read and altered (or corrupted) by any method in the program.
  • On the other hand, if an instance variable is private, a public get method does allow other methods to read the data at will, but the get method can control the way that the data is formatted and displayed.
  • A public set method can control attempts to modify the value of an instance variable so that any necessary conversions are made and the variable assumes only valid values.
  • An excellent example of these concepts is a Date class, that may be implemented as three integer attributes for month, day, and year, or it could be implemented as a Julian data value. The user only perceives the date as a month, day, and year combination regardless of how it is implemented under the hood.

In addition, the designer can provide a get method for an instance variable but not a set method, thereby creating a read-only instance variable.


Here are some examples of accessor and mutator methods for the above class.

As explained, a getter method is one that returns a value, but lets us access it like we would a property.

In order to make a getter, we must make sure that our method:

// getter method
get name() {
return this.#name;
}


This next example of a getter method uses template literals which require backticks

// getter method
get studentID() {
return `The student ID is ${this.#studentID}.`;
}


A setter method makes it possible to set class attributes, but instead of having to invoke a given method as a function, we can access it as a property of a given class.

In order to make a setter, we must make sure that our method:

set name(name) {
this.#name = name;
}

set studentID(ID) {
this.#studentID = ID;
}


Class Methods

You can also create methods to implement other class behaviors.

Class methods do not require the function keyword.

The following examples show class information returned first using a template literal and second by concatenating a string:

studentInfo() {
return `${this.#studentID}: ${this.#name}`;
}

studentInfo2() {
return this.#studentID + ': ' + this.#name;
}


Demo

This is a link to a demo program testing the above code.

class Student {
#name;
#studentID;
constructor(sname, sID){
this.#name = sname;
this.#studentID = sID;
}

// getter method
get name() {
return this.#name;
}

// getter method
get studentID() {
return `The student ID is ${this.#studentID}.`;
}

set name(name) {
this.#name = name;
}

set studentID(ID) {
this.#studentID = ID;
}

set studentID(ID) {
this.#studentID = ID;
}

studentInfo2() {
return this.#studentID + ': ' + this.#name;
}
}


The following figure provides an infographic on the JavaScript class declaration.

Class figure.

Instantiating an object and invoking its behaviors will be explained in the next section.

Section 4: Instantiation

The term instantiation is used when an object is created from a class.

Just as an instance of a primitive type is called a variable, an instance of a class is called an object.

To instantiate an object in JavaScript, use let or const, along with the keyword new.

This first example creates a new instance of the StudentClass and calls the no-argument constructor supplied by JavaScript.

let studentInstance1 = new StudentClass();

If you want to initialize the object's attributes, you can call the setter methods:

studentInstance1.name = "Heidi Clare";
studentInstance1.studentID = "000763728";

Notice again that calls to setter methods do not require parentheses or arguments in parentheses.

In the second example, a new instance, or object, of the StudentClass is created and the declared constructor is invoked to initialize the class attributes.

let studentInstance2 = new StudentClass("Wally Ballou", "123456789");

Note that even if the class attributes have been initialized, they can be changed by invoking the mutator method.

One of the preceding examples demonstrated calling a setter method, and the following shows a getter invocation.

alert(studentInstance2.name);
alert(studentInstance2.studentID);


Here is an example of a function to test the above code. It is called from the onload event like <body onload="test();">

function test() {
let studentInstance1 = new StudentClass();
studentInstance1.name = "Heidi Clare";
studentInstance1.studentID = "000763728";
alert(studentInstance1.studentInfo2());

const studentInstance2 = new StudentClass("Ty Coon", "123456789");
alert(studentInstance2.name);
studentInstance2.name='Reid Enright';
alert(studentInstance2.name);
alert(studentInstance2.studentID);
alert(studentInstance2.studentInfo());
}

Section 5: Abstraction

An object-oriented programming language is generally characterized by four core features – abstraction, polymorphism, inheritance, and encapsulation.

(JavaScript supports all of these features in some fashion.)

from Introduction to Objects (CS 2282)

So the four principle concepts upon which object-oriented design and programming rest are Abstraction, Polymorphism, Inheritance and Encapsulation (easily remembered as A-PIE). These notes introduce and explain these four concepts.

While A-PIE may be easy to remember, the principles build upon one another in the following order: Abstraction, Encapsulation, Inheritance and Polymorphism.


Abstraction Defined

Abstraction, as a process, denotes the extraction of the essential details about an item, or a group of items, while ignoring the inessential details, while abstraction, as an entity, denotes a model, a view, or some other focused representation for an actual item.

Data abstraction can be implemented in OO languages through the creation of classes, which represent abstract data types (ADTs).

Abstraction can also be viewed as taking an idea, concept, or object and expressing it in terms of the constructs provided by the programming language.

Object-oriented languages allow programmers to define a higher-level abstraction tailored to the problem at hand.


Scenario

In order to process something from the real world, we have to extract the essential characteristics of that object. Consider the following example.

Class figure.

Data abstraction can be viewed as the process of refining away the unimportant details of an object, so that only the useful characteristics that define it remain. Evidently, this is task specific.

Formulated as such, abstraction is a very simple concept and one that is integral to all types of computer programming, object-oriented or not.


Functional Abstraction

The process of modeling functionality suffers from the same pitfalls and dangers as found when abstracting the defining characteristics, that is, unnecessary functionality may be extracted, or alternatively, an important piece of functionality may be omitted.

Section 6: Encapsulation

Encapsulation, is one step beyond abstraction because it extends the idea of abstraction by modeling and linking the functionality of that entity.

As a principle, encapsulation ensures that you are not required to know how something is implemented in order to be able to use it.

The implication of encapsulation is that you can build anything any way you want, and if the implementation is later changed it will not affect other components within the system, provided that the interface to that component does not change.


Encapsulation Defined

Encapsulation is the act of grouping into a single object both data and the operations that affect that data.

Encapsulation links the data to the operations that can be performed upon the data, and hence ensures that data is only used in an appropriate manner.

In object-oriented programming, encapsulation is the inclusion within a program object of all the resources needed for the object to function – basically, related data and the methods that manipulate those data.

Classes are useful for the following reasons:

Consider a Student class.

Encapsulation is intended to provide a "cover" or "coating" that hides the internal structure of the class from the environment outside.


Information Hiding

Encapsulation alone does not prevent the data of an object from being manipulated by functions other than the methods bound to the class.

Information hiding is the technique of concealing implementation details within the objects themselves, while the interface remains visible.

Information hiding requires the visibility of data to be restricted to within the (encapsulated) module.

Information hiding is accomplished through the use of the # (private) access modifier and the use of accessor and mutator methods designed to carefully control access to private data.

If instance variables are not declared as private, they are encapsulated but not hidden. Encapsulation without information hiding is inadequate.

Section 7: Inheritance

The third of our four core OO features, inheritance, is the process of creating new classes, called derived classes, from existing or base classes.

Inheritance is one of the most powerful features of object-oriented programming.

The figure below shows a base class called Student, along with two derived classes called Undergrad and Grad.

Inheritance figure.

For the sake of brevity (and laziness), the sample code used to test the features of inheritance used the more simple inheritance structure shown below:

Inheritance figure.

Recall the StudentClass class that we defined in an earlier section:

class StudentClass {
#name;
#studentID;
constructor(sname, sID){
this.#name = sname;
this.#studentID = sID;
}

// getter method
get name() {
return this.#name;
}

// getter method
get studentID() {
return `The student ID is ${this.#studentID}.`;
}

// getter method
get ID() {
return `The student ID is ${this.#studentID}.`;
}

set name(name) {
this.#name = name;
}

set studentID(ID){
this.#studentID = ID;
}

set studentID(ID){
this.#studentID = ID;
}

studentInfo() {
return `${this.#studentID}: ${this.#name}`;
}

studentInfo2() {
return this.#studentID + ': ' + this.#name;
}
}

To create a class that inherits from another, we use the extends keyword.

class GradstudentClass extends StudentClass {
#undergradSchool;   // instance variable new to this derived class

constructor(name, ID, undergradSchool){
super(name, ID);   // call to inherited constructor
this.#undergradSchool = undergradSchool;
}
}

The keyword extends is used to indicate that we want to inherit from the parent class StudentClass.

The constructor of the derived class first calls the constructor from the base class to initialize the attributes inherited from the base class, and then initializes any attributes added by the derived class.


Setter and Getter methods

Setter and getter methods can be added for any attributes declared in the derived class.

If setter and getter methods were provided for attributes in the base class, they do not have to be provided again in the derived class since they are inherited.

Setter and getter methods may be provided for any attributes added by the derived class.


Other Methods

Additional methods can be defined as needed, to complement those inherited from the base class.

Here are some guidelines to remember when writing methods in a derived class:


Here is the inheritance demo. Please view the page source or see the code below to see what can and cannot be done in a derived class. The page source includes comments that explain what is happening.

class GradstudentClass extends StudentClass {
#undergradSchool;

constructor(name, ID, undergradSchool){
super(name, ID);
this.#undergradSchool = undergradSchool;
}

set undergradSchool(school) {
this.#undergradSchool = school;
}

get undergradSchool() {
return this.#undergradSchool;
}

gradstudentDetails() {
return super.name + " (" + super.ID + ") graduated from " + this.#undergradSchool +".";
}

gradstudentInfo() {
return this.studentInfo() + " graduated from " + this.#undergradSchool +".";
}
}


Method Overriding

In many situations we have base class methods that might need to be modified in the derived class.

A derived class can redefine a base class method using the same signature, that is, methods of a derived class can have the same name as those in the base class.

A method's signature refers to the number, data type, parameter type, and order of the arguments in the parameter list.

Method overriding: When the same method name and signature exists in both the base class and the derived class, when that method is called in the derived class the method defined in the derived class will be executed, that is, the derived class method overrides the base class method.

By default, all methods specified in the base class are inherited in the derived class, so the GradstudentClass inherits all methods declared in the StudentClass, including studentInfo(), which returns all info about the student.

Why is an esoteric subject like overriding being explored? Because it is essential to polymorphism.


Inheritance References

Here are some useful links that discuss inheritance:

Section 8: Polymorphism

Polymorphism refers to methods that can have multiple forms.

The term binding, or method lookup, refers to the act of selecting which method will be executed in response to a particular invocation.

Dynamic binding refers to binding that is performed at run time.

In case of dynamic binding, we do not have to know the exact type of an object. It is enough to be sure that instance will be of some type or has a superclass that contains needed functionality. In such cases the exact behavior is determined at run-time. That important ability simplifies code in cases where many objects have the same interface with different implementations.

Polymorphism allows a number of different classes of objects to respond to the same request by providing subclasses with methods with the same name and signature as a method in the superclass.

With polymorphism, the programmer can deal in generalities and let the execution-time environment concern itself with the specifics.

Polymorphism promotes extensibility.


Example


Here is a polymorphism demo. You can view the page source or see the code below to see how polymorphism is implemented.

// Bird class - the base class
class Bird {
#type;
#color;
#locale;
#diet;

move() {
return "The bird is flying.";
}

makesNoise() {
return "The bird is making noise.";
}

eat() {
return "The bird is eating.";
}
}

// derived class Penguin - overrides move method to swim
class Penguin extends Bird {
#height;
#territory;

move() {
return "The penguin is swimming!";
}

dive() {
return "The penguin is diving!";
}
}

// derived class Kiwi - overrides move method to run
class Kiwi extends Bird {
#endangered;
#predator;
#subspecies;

move() {
return "The kiwi is running!";
}

dig() {
return "The kiwi is digging a nest.";
}
}

// derived class Hummingbird - does not override move method because hummingbirds fly
class Hummingbird extends Bird {
#size;
#weight;

migrate() {
return "The hummingbird is going south.";
}

hover() {
return "The hummer is hovering.";
}
}


function test() {
let pBird = new Penguin();
alert(pBird.move());
alert(pBird.dive());

// Create array of birds
let birdList = [new Penguin(), new Hummingbird(), new Kiwi()];
alert(birdList[0].move());
alert(birdList[1].move());
alert(birdList[2].move());
}


Here is another polymorphism demo focusing on savings and checking accounts. Again, please view the page source to see how polymorphism is implemented or click on the example page.


Here is another discussion of How JavaScript works: 3 types of polymorphism with an example.

Note that we have demonstrated that the four core features of object-oriented programming – abstraction, polymorphism, inheritance, and encapsulation – are supported by JavaScript.

Section 9: Alternate Approaches

You can define objects in several other ways in JavaScript.

It is common to see an object defined using an object literal, as follows:

var testStudentObj1 = { name: "Barry Cade", studentID: "112233445" };

Here are some additional examples.


.

Reflecting on the above figure, using the ES6-based approach to implementing objects in JavaScript may help to make the concepts more familiar to students.

Further, you must keep in mind that the new Class simply offers a more familiar syntax on prototypal classes, and under the hood, ES6 Classes are still based on prototypal inheritance.

Here is the 4430 professor's material explaining prototype based objects, or you can view his PowerPoint presentation below.

Section 10: Final Thoughts

One benefit of the object-oriented approach is the close correspondence between the real-world things being modeled by the program and the objects in the program.

This makes it easy to conceptualize a programming problem. You figure out what parts of the problem can be most usefully represented as objects, and then put all the data and methods connected with that object into the class specification.

In some situations it may not be obvious what parts of a real-life situation should be made into objects because there are no hard and fast rules. Often you proceed by trial and error. You break the problem into objects in one way and write trial class specifiers for these objects. If the classes seem to match reality in a useful way, you continue. If they don't, you start over, selecting different entities to be classes. The more experience you have with OOP, the easier it will be to break a programming problem into classes.

Some of the benefits of object-oriented programming are probably not apparent at this point because OOP was devised to cope with the complexity of large programs. Smaller programs have less need for the organizational power that OOP provides. The larger the program, the greater the benefit.

Section 11: References Used for Notes

You may notice that the preceding notes consist of bits and pieces of all of these sites.