Intro
Object-Oriented Programming is a programming paradigm based on the concept of objects, which combine data (properties) and behavior (methods). OOP focuses on modeling real world entities as objects and promotes modularity, reusability, and maintainability.
Disclaimer: Most of the javascript content for my writings will come from the book - Speaking JavaScript. This is fantastic book!
If you are a C++ or Java programmer then you are already aware of Object Oriented Programming and how we utilize it for writing scalable and maintainable code. With the rise of web applications we are writing a lot of JS and with time this will keep growing. In this article we will explore how we achieve OOP in JavaScript using the language constructs while JavaScript is not a object oriented programming language at all!
Classes & Objects
In JS we use the constructor functions to define structure of a class and then to create the instance of that class we use the new operator. Now constructor functions are not any special functions they are the usual functions with some special restrictions.
1 | // constructor function Person |
Note: If we miss the new* keyword, this will not create any object. When we use new, it creates the this for the Person object and implicitly returns this keyword.
Methods
We can add behavior of the objects as well by adding methods to the class definition along with properties (like name & age).
1 | // constructor function Person |
Now we have behaviors added to our objects, but there’s an issue when adding methods directly inside function constructors in JavaScript. Every object created using the new operator gets its own copy of each method, which leads to unnecessary memory usage. To avoid this, we place the methods on the constructor’s prototype instead. This way, all objects share the same method through the prototype chain, rather than storing separate copies.
Prototype
This prevents each object from storing its own copy of the method, reducing memory usage. Instead, every object accesses the method via the prototype chain. So instead of attaching methods inside the constructor, we attach them to the prototype like this:
1 | function Person (name, age) { |
We can validate that both the objects are referring to the same method instead of making copies via:
1 | var john = new Person("John", 20); |
The new Operator
So we can now say that the new operator:
- Creates an empty object.
- Set the keyword
thisto the currently created object. - Link the prototype chain to the object created.
- Returns
thisimplicitly.
Object.create()
In JavaScript, all objects ultimately inherit from Object.prototype, which sits at the top of the prototype chain. The built-in Object constructor provides several commonly used methods such as toString(), create(), freeze(), and so on.
We can use Object.create() passing it an instance of some other class and it will return an empty object of that class having the prototype linked to it.
Here’s an example:
1 | function Person (name, age) { |
In the code above, jemmy is created using Object.create(marry). This creates a new empty object whose prototype is set to the object marry. As a result, jemmy does not have its own properties, but it can access name, age, and the methods defined on Person.prototype through the prototype chain.
When we inspect the prototype of jemmy, we see that it points directly to marry, not to Person.prototype.
1 | Person {} |
If we want jemmy to directly use Person's prototype, we can use something like:
1 | // create jemmy with Person.prototype as its prototype |
Prototypal Inheritance
JavaScript used a prototype based inheritance model rather than classical class based inheritance. Objects inherited directly from other objects through the prototype chain. The example above of Object.create is an example of inheritance in JS. Also one thing to note is that we actually try to simulate inheritance using JS language constructs.
Function Constructor
Let’s see how we can achieve inheritance using constructor functions and prototypes.
1 | function Person(name, age) { |
Calling the parent constructor ensured that instance specific properties (like name & age) were copied onto the child object. This is how method lookup worked internally:
1 | student -> Student.prototype -> Person.prototype -> Object.prototype -> null |
Reset Constructor
Why we have to reset the Student constructor: Student.prototype.constructor = Student;?
When we use Object.create() to set up inheritance between constructor functions, we overwrite the child’s prototype object. Due to this, the default constructor property is lost and ends up pointing to the parent constructor instead of the child. Without the reset, Student objects appear to be Person objects.
Polymorphism
Since we discussed Inheritance, let’s also cover polymorphism in JS. The definition of polymorphism states that it is the ability of one thing to take multiple forms. This means a single name (method, function, operator) can be used in different ways or represent different behaviors.
We saw the greet() method above that is coming from Person to Student. But let’s tweak the greet method to work differently for Students. We can do this by method overriding.
1 | function Person(name, age) { |
On running the code we can see that the methods are same but working differently for different objects.
1 | Hello, I'm Jerry |
Encapsulation
Encapsulation is the practice of bundling data (properties or variables) and the methods that operate on that data into a single unit called class while restricting direct access to some of the object’s internal details.
For example A BankAccount class hides the balance and allows access only through methods like credit() or debit(). We have already seen that we can bundle method and properties using constructor functions and prototype but let’s see how we enable data or method hiding.
Here’s the code demonstration of using a private variable balance:
1 | var BankAccount = (function () { |
IIFE
IIFE is called Immediately Invoked Function Expression which is a function that runs as soon as it is defined. In the example above, the first line BankAccount is an IIFE.
Closures
Closure is when a function remembers variables from its outer scope, even after the outer function has finished executing.
balance is defined inside the constructor, the getBalance and setBalance are inner functions that close over balance Even after the constructor finishes, these functions still remember balance for that instance. This allows true privacy, because nothing outside can access balance directly.
1 | console.log(bank.balance); // undefined |
Abstraction
Abstraction is the process of exposing only the essential features of an object while hiding the complex implementation details. In short, focus on what an object does, not how it does it. In java, abstraction is achieved using interfaces or abstract classes.
For example a Vehicle interface defines a start() method without specifying how different vehicles implement it. let’s look at the code:
1 | function Vehicle(type) { |
In the code above, the drive() method is abstracted from the user. The user need not to know about how the car is driving.
Outro
So we saw how we use the language features of JavaScript like functions, closures, prototype and others to simulate the objet oriented programming techniques. it’s a wrap for now.
I hope you enjoyed reading.