DEV Community

loading...
Cover image for Master object-oriented design in JavaScript to pass any interview

Master object-oriented design in JavaScript to pass any interview

Albert Hadacek
React / Node developer and Software Engineering student
・5 min read

Object-oriented design in JavaScript is one of the most common topics that appear in coding interviews and it is absolutely crucial for every developer to have these concepts under their belt. In this article, we are going to take bottom to top approach. We are gonna explore the benefits of OOP, prototype chain, inheritance, and eventually the ES6 class keyword.

Why do we need object-oriented design?

JavaScript is not a classical class-oriented language like Java or C#, it does not even have classes implemented in the traditional way. Yet, it is possible to use features like the prototype chain to mimic the OOP behavior.

The main reason, why we might want to use object-oriented design in JavaScript is to encapsulate certain functionality together, so it is easier for coders to conceptualize code. We then store that common functionality on objects.

const user1 = {
    name: "Peter",
    email: "peter@domain.com",
    login() {
        console.log(`${user.name} is logged in`)
    }
}

console.log(user1.name) // "Peter"
user1.login() //"Peter is logged in"
Enter fullscreen mode Exit fullscreen mode

The snippet above shows how we group data and properties together to form logical (from human perspective) pieces of code that corresponds to our idea what a user template should look like. The problem with the code above is that to create a new user we would need to copy and paste user1 and change the values so it corresponds to user2. That does not seem very effective. It would be better to have a function that can create users.

Naive implementation of factory functions

A factory function is simply a function that returns a new object. It is also the building block of how OOP actually works in JavaScript because as I mentioned before, traditional classes do not exist in the language.

function userCreator(name, email) {
    const newUser = {}
    newUser.name = name
    newUser.email = email
    newUser.login = function () {console.log(`${newUser.name} is logged in`)}
    return newUser
}

const user1 = userCreator("Peter", "peter@domain.com")

console.log(user1.name) // "Peter"
user1.login() //"Peter is logged in"
Enter fullscreen mode Exit fullscreen mode

We have made good progress. Our factory function is able to produce objects based on a blueprint. The problem is that functions we attach as methods to the newUser object are copied. So if we create 100 users, we will have 100 copies of the same method in memory. That is definitely not something we want. We would like to have a pointer or link to some storage, where all instances of the user object can look for common functionality. Luckily, JavaScript has a solution exactly for that.

Factory functions using prototypes

In JavaScript, we can link objects via the hidden __ proto __ property which means that if some functionality is not on our object directly the interpreter will check if it's in the linked object. Therefore we can keep just a reference to some form of storage. To do that we can use the Object.create() method which allows us to specify which object we want to create the link to as its first argument.

function userCreator(name, email) {
    const newUser = Object.create(functionStorage)
    newUser.name = name
    newUser.email = email
    return newUser
}

const functionStorage = {
    login() {
console.log(`${this.name} is logged in`)
    },
    logout() {
    `${this.name} is logged out`
    }
}

const user1 = userCreator("Peter", "peter@domain.com")

console.log(user1.name) // "Peter"
user1.login() //"Peter is logged in"
Enter fullscreen mode Exit fullscreen mode

Notice that we are using the this keyword in our functionStorage, this will then be evaluated to the object the method was called on.

The new keyword

The previous example basically showed us how to implement object linking in a very manual way. To make it easier, we can use the new keyword which does some parts of the job for us. So, what exactly happens if we invoke a function using the new keyword?

Firstly, it will automatically create an empty object in the local memory of our function and bind this to it.

Secondly, it will create a link between that object and a property prototype on the function. Functions are also objects so they have properties. We can then store methods on that prototype property.

Thirdly, it will return that newly created object.

function User(name, email) {
    this.name = name;
    this.email = email;
}

User.prototype.login = function() {
  console.log(`${this.name} is logged in`)
}

User.prototype.logout = function() {
  console.log(`${this.name} is logged out`)
}

const user1 = new User("Peter", "peter@domain.com")

console.log(user1.name) // "Peter"
user1.login() //"Peter is logged in"
Enter fullscreen mode Exit fullscreen mode

Notice, that we named the function with the first letter capitalized. That is a convention that tells other developers that the function should be invoked using the new keyword.

Inheritance using prototypes

One of the features of traditional object-oriented design is inheritance which allows us to create a general parent class with some core functionality and then extend it. We can have a base class called user, and then child classes like admin or contributor that will share some common functionality but at the same time add functionality that is unique for them.

In this example we create our User factory function similarly to the previous example, then we create a function representing an administrator. Firstly, we need to define our "constructor" which simply means we call the parent function, yet we change the context to the object created by our Admin function which is simply the keyword this because Admin is gonna be invoked using new. Notice, that the User function is not called with the new keyword so it simply assigns the properties to the object which is passed by reference.

Last thing we need to handle is to create the link between Admin.prototype and User.prototype so we have access to the additional functions.

To do that we simply use the Object.create() method and specify the object in our case User.prototype as the link. Then we can add our specific functionality.

function User(name, email) {
    this.name = name;
    this.email = email;
}

User.prototype.login = function() {
  console.log(`${this.name} is logged in`)
}

User.prototype.logout = function() {
  console.log(`${this.name} is logged out`)
}

function Admin(name, email, authorization) {
    User.call(this, name, email)
    this.authorization = authorization
}

Admin.prototype = Object.create(User.prototype)
Admin.prototype.blockUser = function(name) {
    console.log(`User ${name} was deleted`)
}

const admin1 = new Admin("Peter", "peter@domain.com", 1)

console.log(admin1.name) // "Peter"
console.log(admin1.authorization) // 1
admin1.blockUser("John") // "User John was blocked"
Enter fullscreen mode Exit fullscreen mode

The ES6 classes

In 2015 the class keyword was introduced into the world of JavaScript. The syntax is very similar to languages like Java, but under the hood, it behaves completely differently. It is just syntactic sugar for the code we have written so far. In today's code ES6 classes are the go-to, yet knowing the prototype theory behind is essential.

class User {
    constructor(name, email) {
        this.name = name
        this.email = email
    }

    login() {
      console.log(`${this.name} is logged in`)
    }

    logout() {
      console.log(`${this.name} is logged out`)
    }
}

class Admin extends User {
    constructor(name, email, authorization) {
        super(name, email)
        this.authorization = authorization
    }

    blockUser(name) {
        console.log(`User ${name} was blocked`)
    }
}

const admin1 = new Admin("Peter", "peter@domain.com", 1)

console.log(admin1.name) // "Peter"
console.log(admin1.authorization) // 1
admin1.blockUser("John") // "User John was blocked"
Enter fullscreen mode Exit fullscreen mode

Discussion (1)

Collapse
arvindsridharan profile image
Forem Open with the Forem app