JavaScript Tutorial: Objects and Classes in JS

JavaScript Objects and Classes Tutorial: learn how to use classes to make your code better and more scalable

Share This Post

We always code to represent reality. In fact, we want to use our code to achieve real results that have meaning, in the real world. Thus, we need to be able to represent that real-world efficiently. Objects are a neat way to do that, and in this javascript tutorial, we will see how to use them.

If you are starting out with JavaScript just now, maybe objects are not the first thing to learn. Instead, you should start with this introduction, and then be sure to know what functions are. Enjoy the tutorial!

Why JavaScript Objects?

The Problem We Face

What do we mean by representing the real world? An example will clarify that for us. Imagine you want to represent something very real, the classic example is a user of your app. The user will have a name, an email address, a password, and maybe some attributes such as a balance if we are in a banking app. And, of course, our app can have many users, not just one. How do we deal with this information?

Let’s think for a second what would be the best way to represent a user, before introducing objects. Since we have many, we should go with an array, but an array of what? We cannot have an “array of users”, but a user is not a simple piece of information such as a string or a number.

Instead, we can decide to go with an array of arrays. Since each user contains multiple information (name, email, password, balance), we can decide to represent it as an array with four keys. It may look something like this.

const user1 = [
  'John Doe',
  'johndoe@example.com',
  'passwordhash',
  250.00
];

And this will work just fine. Then, if we want to get the name of the user we can simply go with user1[0], while for the balance we use user1[3]. Not extremely intuitive, but manageable.

The Issue with Functions

However, representing data is not the only thing we do. We want to process data, elaborate it. And, as we know, we can use functions for that, functions that accept parameters. So, if we want to create a logout function and execute it on a user we could do something like logout(user1) when calling it.

Now, pay attention to that “logout” function. Think about it for a second. Such a function does not have any sense to exist without a user, so it is tightly coupled with the user. In fact, it will most likely process some fields of the user, so whenever the user model change, we also need to update the function. In other words, our function is coupled with our data. Yet, we define our data, our user, in a different place than the function, definitely not the best.

Objects can help us solve this problem, grouping together data and functions that are strictly related to them.

Creating JavaScript Objects

JavaScript Objects Basics

Defining an object is not much different than defining an array. The difference is that you use curly brackets instead of squared brackets, and you use string keys that you have to pick.

When you define an array, you can list all the items that are part of it, and they will automatically take sequential indexes (or keys), from 0 and going up. Here, keys or indexes are numbers (arr[0], arr[1], and so on).

The syntax to define an object is the following.

var myObject = {
  'myKey1': 'myValue1',
  'myKey2': 'myValue2',
  'myKey3': 3
};

We can easily translate this into our user as follow.

const user1 = {
  'name': 'John Doe',
  'email': 'johndoe@example.com',
  'password': 'passwordhash',
  'balance': 250.00
};

Now, we can access the properties of the object in two ways: with dotted notation or with index notation. The dotted notation is the simpler one, and the one you should strive for all the time. You simply write the name of the object, followed by the property name (key) separated by a dot. Examples are user1.name, user1.email, user1.password and so on.

Since keys are strings, they may potentially contain spaces. If that’s the case, the dotted notation won’t work, and you have to use the index notation, which is like an array (e.g. user['name']). However, avoid spaces in key names as it is a bad practice, and stick with the dotted notation, much clearer.

Tip: one real-world use-case of index notation is if you want to use the value of a variable as a key, like below. That is pretty much the only use case.

var interestingKey = 'email';
console.log(user1[interestingKey]); // johndoe@example.com

Adding Functions

Who said data are just plain data? Data can be functions as well or, more specifically, you can assign a function to a key of an object. So, for example, we can add a logout function like so.

const user1 = {
  'name': 'John Doe',
  'email': 'johndoe@example.com',
  'password': 'passwordhash',
  'balance': 250.00,
  'logout': function() {}
};

Now, the logout property is going to be a function, not just a “normal” piece of data. Functions within an object are also known as methods. In any case, we don’t want to pass the user to our function, otherwise, there is no benefit. Instead, we want our function to automatically have access to all the properties of the object.

We can do that by referencing the special object this. In fact, this is a reserved keyword that means “this very object”. It points to the object that the function is part of. We can use it with the dotted notation to access and modify all the values the way we want it.

So, we can suppose our logout function simply sets the password to null. We can reference it with this.password, creating the following example.

const user1 = {
  'name': 'John Doe',
  'email': 'johndoe@example.com',
  'password': 'passwordhash',
  'balance': 250.00,
  'logout': function() {
    this.password = null;
  }
};

Calling the method from outside is simple. We just have to use the method name in dotted notation, much like a property. However, to that, we need to add the brackets, as we would normally do with a function. If you don’t use the brackets, you will get back the function definition and not its execution.

user1.logout();

JavaScript Classes

Creating objects on the fly as we did so far is a good approach, but has a problem. Every new object we create we have to re-type the keys, which is not really ideal. Classes solve this problem.

What are Classes in JavaScript?

You can think classes are much like object templates. You define the structure of your object once, and then you can use it again and again in your code. As such, as part of the definition, you include all the possible keys and methods that you want to be part of the object.

After all, the name of the keys and the actual functions won’t change from one object of the same type to another. Only data will change.

This is extremely useful when we want to update our code and our logic. We have all our logic in a single, self-contained element so that we can do all our updates in a single place.

Defining a Class in JavaScript

A class must have at least a name and a constructor. A constructor is nothing more than a special function gets called when you create an object following the template defined as part of the class. When naming a class, it is a best practice to have the first letter uppercase.

Objects that originate from a class have a special name: instances of that class. In fact, the class itself is something abstract, it is just the definition – much like a function is how to execute something, and not its actual execution.

The syntax to create a class is the following.

class MyClass {
  constructor() {
    // Some logic
  }
}

Inside the constructor, you define and set initial default values to all the properties of the class. To do such a thing, you use the keyword this. You can also accept some parameters in the constructor, to personalize initial values if you want. If we were to create a class for our user, we can do something like that:

class User {
  constructor() {
    this.name = null;
    this.email= null;
    this.password= null;
    this.balance = 0;
  }
}

Remember, this is just the template and we are setting default values. We are not setting the values of a specific user.

Functions in JavaScript Classes

If we want to add a function in our class, a method to be specific, we need to do it within the class, but outside the constructor. We can give our method a name, and it can accept some parameters if needed. The definition is simple, similar to the one of a function but without needing the word function beforehand.

Considering this, we can easily add the logout() method as detailed below.

class User {
  constructor() {
    this.name = null;
    this.email= null;
    this.password= null;
    this.balance = 0;
  }

  logout() {
    this.password = null;
  }
}

Creating an Instance from a Class

As we saw, classes are only a definition, a template. This means that, unless we apply that template, they are of little use. By applying the template, we really mean creating objects originating from that class, creating instances of it.

To create an instance of a class, we need to use the special keyword new. Below an example.

const user1 = new User();

And now, we can access its property and methods in the dotted notation as we did with “normal” objects, such as user1.name or user1.logout().

You see that to instantiate the object we use the class name followed by brackets. Within these brackets, we can list the parameters of the class’ constructor if there are some parameters. With this special syntax, we are doing nothing more than calling the constructor to construct a new object.

Remember, the keyword this in a class refer to the object of the current instance. This means that it will always work as expected because it will consider the properties and methods of the instance where the methods are called on.

Advanced Features of Classes

With all that we saw so far, we can pretty much create complex representations of reality. However, classes have some really nice extra features that can be useful from time to time.

Static Methods & Classes

We know that you can call the methods on a class instance. However, sometimes you may want something different. You may want to use a class just as a group of functions, which doesn’t need to be instantiated as it does not work on specific that.

In that case, you can use static methods that you can call by referencing the class name, and with no need to create an instance. To do this, we need to add the keyword static before defining our method. Take a look at the following example.

class MyLibrary {
  static pi() {
    return 3.14;
  }

  static multiply(a, b) {
    return a * b;
  }
}

MyLibrary.pi(); // 3.14
MyLibrary.multiply(10, 5); // 50

However, in static methods, you cannot use the keyword this. That is because they do not work on an instance, so there is no “this very object” to refer to. However, you may call a static method on an instance, you cannot use the keyword in their definition because they are supposed to work even without an instance.

Getter

Sometimes, a function returns always a value and does not accept any parameter. You may consider it as a calculated property, a read-only property that you can calculate on the fly. To render that, you can simply add the keyword get in front of the method definition, like below.

class User {
  constructor() {
    this.name = null;
    this.email= null;
    this.password= null;
    this.balance = 0;
  }

  get hasPassword() {
    return this.password != null;
  }
}

Now, after this example, we can get the value of hasPassword as a property, without needing to specify the brackets after it. In fact, the correct approach is user1.hasPassword, while user1.hasPassword() will throw error. Obviously, we cannot assign a value to it, so user1.hasPassword = true will throw error as well.

We may also combine get with static to create a computed static property (static get).

Inheritance

This is probably one of the most important advanced features you will ever need. That is because it allows you to represent even more complex situations and abstractions.

In some cases, you have a class that is slightly different from another. We can say it is based on it, it is a more specific version of it. A dog is an animal, and a Volvo is a type of car, which is a type of vehicle. An administrator is a user with some extra power, and so on. Inheritance allows us to represent those relationships between classes.

The advantage of that is that we can create a base class, which is a generic class containing only the generic stuff. Then, we can add an additional class that extends it: they inherit (so they have at their disposal) the properties and methods of the base class, but they also add their own flavor.

To say that your class should inherit from another, you use the keyword extends followed by the base class name in the class definition. A class may extend only one other class, but you can create a chain of inheritance.

class Animal {
  constructor() {
    this.weight = null;
  }
}

class Dog extends Animal {
  bark() {
    return 'Woff';
  }
}

class Cat extends Animal {
  meow() {
    return 'Meow';
  }
}

class Chihuahua extends Dog {
  // ...
}

The advantage of this is that we define the common stuff in a single place, the base class. However, we don’t need to limit ourselves to just add features to the base class. We can override some of its behavior simply by re-defining the methods.

Override & Super

To override a method, we simply redefine it. Whenever we will instantiate an object, the method defined in the most specific class wins. In other words, if you have a method in your base class, and the same method in your child class, the one in the child class will prevail.

So, in the following example we change the constructor to have the color on top of the weight for dogs.

class Animal {
  constructor() {
    this.weight = null;
  }
}

class Dog extends Animal {
  constructor() {
    this.weight = null;
    this.color = null;
  }
}

We need to re-define the weight because if we override the constructor, the original one will not be called. However, this is not ideal because we are repeating the same code (this.weight = null) twice. If at some point, we decide to change the initial value, we will have to update it in both classes. Not ideal. Instead, we have a better option thanks to super.

In JavaScript, super is a special keyword that refers to the base class, which is also known as the superclass. Much like this refers to this very instance, super refers to the base class, and has access to all its methods.

So, the best approach here would be to override the constructor but also call the base constructor (or super constructor) as the first thing in our override. The previous example can translate into the following code, which is better.

class Animal {
  constructor() {
    this.weight = null;
  }
}

class Dog extends Animal {
  constructor() {
    super();
    this.color = null;
  }
}

Since we are calling the constructor, we use super(). If we were to call other “normal” methods, we would use the dotted notation such as super.speak() or super.bark().

Classes and Objects for Full Stack Developers

This JavaScript tutorial on classes and objects is part of a larger series of tutorials to transform you into a full stack developer. To do that, you really need to practice what you learn, and so we offer an exercise as part of every tutorial. With all those exercises, we will build a pretend bakery store website that you can see on GitHub.com at alessandromaggio/full-stack-course.

Obviously, as part of this tutorial, you will have to do an exercise on classes and objects.

The Assignment

For this assignment, you need to create the representation of two entities: user and administrator. Each user will have a name, an email, a password, and the last login date. Instead, administrators have all the fields of users, and on top of that have privileges, which should be represented as a list of strings.

Each user must have a login function, that accepts a password as input and checks it against the actual password. On the other hand, each administrator must have an “hasPrivilege” function that checks if a given privilege string is contained among the privileges, and return true if that’s the case.

Finally, you should create a user named Jane Doe with an email of jane@example.com and a password set to “test”, and an administrator named Admin with no email and password set to “secret”. The administrator must have the privileges: “create”, “edit”, “delete”.

All your code should go in the file script.js. Even if you are not following the full stack development course you can do that in a simple javascript file.

As always, try to work it out on your own before checking the solution. Then, only once you are ready, continue with reading the solution below.

The Solution

Okay, so here we go the solution. Be sure to continue reading only if you did your best on your own first!

So, according to the requirement, it is clear that the administrator is a special type of user (it has the same fields), and thus it extends it. As such, the first thing we want to do is create the user class, which we name User to follow the uppercase best practice. It looks like this.

class User {
  constructor() {
    this.name = null;
    this.email = null;
    this.password = null;
    this.lastLogin = null;
  }

  login(pass) {
    return this.password === pass;
  }
}

Then, we need to create the class for administrators. Also there, the name is Administrator to follow the uppercase rule. Here, besides the extension, we call the super constructor and then we add the privileges array (a list of strings per requirement).

In addition, our function to search a privilege will loop through the privileges we have and return true in case it finds a correspondence, otherwise false.

class Administrator extends User {
  constructor() {
    super();
    this.privileges = [];
  }

  hasPrivilege(privilege) {
    for(let i = 0; i < this.privileges.length; i++) {
      if(this.privileges[i] === privilege) return true;
    }
    return false;
  }
}

Finally, we need to create our instances as below.

const user = new User();
user.name = 'Jane Doe';
user.email = 'jane@example.com';
user.password = 'test';

const admin = new Administrator();
admin.name = 'Admin';
admin.password = 'secret';
admin.privileges = ['create', 'edit', 'delete'];

Feel free to check the complete solution in one file by looking at this commit on GitHub.com.

The Wrap Up

Objects and classes are probably the most important concept you need in programming. They allow you to represent complex structures, write better code, and simply be at pace with modern times. No programmer can subtract herself from learning it.

As such, this JavaScript tutorial gave you the basics to navigate this complex world of objects and abstractions. By reading this tutorial, you now have a full understanding of everything you need in JavaScript to actually start working on “real” stuff, such as manipulating the content of the page. And that is where we can go next.

Picture of Alessandro Maggio

Alessandro Maggio

Project manager, critical-thinker, passionate about networking & coding. I believe that time is the most precious resource we have, and that technology can help us not to waste it. I founded ICTShore.com with the same principle: I share what I learn so that you get value from it faster than I did.
Picture of Alessandro Maggio

Alessandro Maggio

Project manager, critical-thinker, passionate about networking & coding. I believe that time is the most precious resource we have, and that technology can help us not to waste it. I founded ICTShore.com with the same principle: I share what I learn so that you get value from it faster than I did.

Alessandro Maggio

2021-02-11T16:30:52+00:00

Unspecified

Full Stack Development Course

Unspecified