A Basic Guide to Object-Oriented Programming (OOP) patterns in JavaScript

Prototypal Inheritance, Constructor function, Prototype Chain, ES6 Classes, Object.create() and Inheritance between “Classes”.

Ayush Verma
Towards Dev

--

Photo by Markus Spiske on Unsplash

What Is Object-Oriented Programming?

Object-Oriented Programming(OOP) is a programming paradigm based on the concept of objects. And paradigm simply means the style of the code,
how we write and organize code.

We use objects to model (describe) aspects of the real world, like a user or a to-do list item, or even more abstract features like an HTML component or some kind of data structure.

Objects may contain data (properties) and also code (methods). By using objects, we pack all the data and the corresponding behavior into one big block.

In OOP objects are self-contained pieces/blocks of code like small applications on their own. Objects are building blocks of applications and interact with one another. Interactions happen through a public interface (API). This interface is basically a bunch of methods that a code outside of the objects can access and use to communicate with the object.

OOP was developed with the goal of organizing code, to make it more flexible and easier to maintain.

Three ways of implementing Prototypal Inheritance

1. Constructor function

  • Technique to create objects from a function.
  • This is how built-in objects like Arrays, Maps, or Sets are actually implemented.

2. ES6 Classes

  • Modern alternative to constructor function syntax.
  • “Syntactic sugar”: behind the scenes, ES6 classes work exactly like constructor functions.
  • ES6 classes do NOT behave like classes in “classical OOP”.

3. Object.create()

  • The easiest and most straightforward way of linking an object to a prototype object.

Constructor Functions and the ‘new’ Operator

  • In JavaScript, there’s really no difference between a “regular” function and a constructor function. They’re actually all the same. But as a convention, functions that are meant to be used as constructors are generally first letter capitalized.
  • In JavaScript, all functions are also objects, which means that they can have properties. And as it so happens, they all have a property called `prototype`, which is also an object.
  • Any time you create a function, it will automatically have a property called prototype, which will be initialized to an empty object.
  • prototype is a property of a Function object. It is the prototype of objects constructed by that function. It is used to build __proto__ when you create an object with new.
  • __proto__ is an internal property of an object, pointing to its prototype. It is the actual object that is used in the lookup chain to resolve methods.

instanceof —operator tests to see if the prototype property of a constructor appears anywhere in the prototype chain of an object. The return value is a boolean value.

isPrototypeOf — method checks if an object exists in another object’s prototype chain.

hasOwnProperty — method returns a boolean indicating whether the object has the specified property as its own property.

Now let’s see how prototypal inheritance i.e above jonas.calcAge() works under the hood:

Prototype Chain

JavaScript uses an inheritance model called “differential inheritance”. What that means is that methods aren’t copied from parent to child. Instead, children have an “invisible link” back to their parent object. More commonly, it’s referred to as the “prototype chain”.

console.log(jonas.__proto__); 
//{ species: "Homo sapiens", calcAge: f, constructor: f}
console.log(jonas.__proto__.__proto__);
//{ constructor: f, _defineGetter_: f, _defineSetter_: f, hasOwnProperty: f, ....}
console.log(jonas.__proto__.__proto__.___proto__);
//null
console.dir(Person.prototype.constructor);
//constructor property points back at person
const arr = [1,2,3,4,5];
console.log(arr.__proto__); //contains all the built-in array methods
console.log(arr.__proto__ === Array.prototype); //true
console.log(arr.__proto__.__proto__); //all object properties
console.log(arr.__proto__.__proto__.__proto__); //null
const h1 = document.querySelector("h1");
console.dir(h1); //object
console.log(x=> x*2); //object

We can add new methods to this prototype and all the arrays will then inherit it.

Array.prototype.unique = function() {
return [...new Set(this)];
};
let arr = [1,2,3,2,1,4,5];
console.log(arr.unique()) // [1,2,3,4,5]

ES6 Classes

Classes in JavaScript do not work like traditional classes in other languages like Java or C++ instead, classes in JavaScript are just syntactic sugar of Constructor Functions. They still implement prototypal inheritance behind the scenes, but the syntax makes more sense to people coming from other programming languages. And that was basically the goal of adding classes to JavaScript.

Some important points to note for classes:

  • Classes are not hoisted even if they are class declarations.
  • Just like functions classes are also first-class citizens, which means that we can pass them into functions and also return them from functions. That is because classes are really just a special kind of function behind the scenes.
  • The body of a class is always executed in strict mode. Classes are executed in strict mode even if we didn’t activate it for our entire script, all the code that is in the class will be executed in strict mode.

Constructor functions are not like old or deprecated syntax. It's 100% fine to keep using them. This is more a question of personal preference.

Setters and Getters

Every object in JavaScript can have setter and getter properties. And we call these special properties assessor properties, while the more normal properties are called data properties. Getters and setters are basically functions that get and set a value.

For objects:const account = {
owner: 'Ayush',
movements: [100,50, 300, 40],

// read something as a property
get latest(){
return this.movements.slice(-1).pop();
},

set latest(mov){
this.movements.push(mov);
}
}
console.log(account.latest); //40
account.set = 60;
console.log(account.movements); //[100, 50, 300, 40]

Classes do also have getters and setters, and they do indeed work in the exact same way. Getter is indeed just like any other regular method that we set on the prototype. Setters and getters can actually be very useful for data validation.

For classes:class PersonCl{
constructor(firstName, birthYear){
this.firstName = firstName;
this.birthYear = birthYear;
}

// Method will be added to .prototype property
get age (){
return 2020 - this.birthYear;
}
}
const ayush = new PersonCl('Ayush','1992');
console.log(ayush.age); //28

Static Methods

Now a good example to actually understand the static method is the built-in Array.from() method. The Array.from() method converts any array-like structure to a real array.

Array.from(document.querySelectorAll('h1'));
//[h1]

This from() method here is really a method that is attached to the Array constructor. So we could not use the from method on an Array because this from method is not attached to the prototype property of the constructor. Therefore all the arrays do not inherit this method.

[1,2,3].from();
//Uncaught TypeError: [1,2,3].from is not a function

Another static method is Number.parseFloat() and it's static on the Number constructor. So it's not available on numbers, but only on this very constructor.

Number.parseFloat(12);
//12

These are some good examples that we understand what a static method is. And we usually use these kinds of helpers, that should be related to a certain constructor. Now let’s do that for both or constructor function and also for the class.

Object.create()

The third way is to use a function called Object.create(), which works in a pretty different way than constructor functions and classes work. Now, with Object.create(), there is still the idea of prototypal inheritance. However, there are no prototype properties involved. Nor any constructor functions or the new operator. So instead, we can use Object.create() to essentially manually set the prototype of an object to any other object that we want.

const PersonProto = {
calcAge (){
console.log(2020 - this.birthYear);
}
}
const ayush = Object.create(PersonProto);
console.log(ayush); //{}
//empty object and in the prototype we have calcAge.
ayush.name = 'Ayush';
ayush.birthYear = 1992;
ayush.calcAge(); //28

When we use the newoperator in constructor functions or classes, it automatically sets the prototype of the instances to the constructors, prototype property. This happens automatically.

On the other hand, with Object.create(), we can set the prototype of objects manually to any object that we want. And in this case, we manually set the prototype of the steven object to the PersonProto object.

Now the two objects are effectively linked through the proto property, just like before. Now looking at properties, or methods in a prototype chain works just like it worked in function constructors, or classes. And so the prototype chain, in fact, looks exactly the same here. The big difference is that we didn’t need any constructor function, and also no prototype property at all, to achieve the exact same thing. This is actually the least used way of implementing prototypal inheritance.

const PersonProto = {
calcAge (){
console.log(2020 - this.birthYear);
},

init(firstName, birthYear){
this.firstName = firstName;
this.birthYear = birthYear;
}
}
const ayush = Object.create(PersonProto);console.log(ayush.__proto__ === PersonProto); //trueconst anu = Object.create(PersonProto);
anu.init('Anu', 1990);
anu.calcAge(); //30

Inheritance Between “Classes”

Constructor Functions

ES6 Classes

Object.create()

In this version, we don’t even worry about constructors anymore, and also not about prototype properties, and not about the new operator. It’s really just objects linked to other objects. And it’s all really simple and beautiful.

In this technique with Object.create(), we are, in fact, not faking classes. All we are doing is simply linking objects together, where some objects then serve as the prototype of other objects. Although ES6 classes and constructor functions are actually way more used in the real world.

Summary

I hope you have found this useful. Thank you for reading!

For more information on this topic, I would highly recommend the Udemy JavaScript course: —
https://www.udemy.com/course/the-complete-javascript-course/

--

--