Start practicing OOP in JavaScript today!

Start practicing OOP in JavaScript today!

You can find the same content written in Portuguese here.


In this article, I will show you the fundamentals of object-oriented programming (OOP) in the JavaScript language. Of course, it is supposed to be just an introduction to this subject, but you will learn how to use objects, create entities, create inheritance, and scoping your code better.

Man saying "OMG! I can't wait!"

Let's start with the object concept.

Objects

Before we go deep into object-oriented programming, it is worth understanding what an object is.

In JavaScript, the object can be interpreted as a dictionary, as it has a set of keys and values.

For example, if you want to know what the word "inspiration" means, look for it in a dictionary. So, find out what the explanation is corresponding to it. JS is the same. If you want to know the value of a certain key, or property, of an object, refer to it.

Syntax

The object in JS is represented by curly braces ({}). Following the previous example, suppose you want to create a variable whose value is a small dictionary of words. Here's how you could do it:

let dictionary = {
  inspiration: 'Meaning of inspiration',
  coffee: 'Meaning of coffee'
};

Okay, we have our dictionary. But how do we look for the meaning of "inspiration" within it?

let inspirationMeaning = dictionary.inspiration;

If we print the value of the variable inspirationMeaning on the screen or the console, the value obtained will be "Meaning of inspiration". Much simpler than browsing a dictionary, don't you think?

Man agreeing

Here we go

Now, another example. Let's say you want to develop a racing game. For this, each car will have the information name, current speed and maximum speed, and some actions, such as accelerating and braking. How can we do that?

let car1 = {
  // properties
  name: 'Car 1',
  currentSpeed: 0,
  maxSpeed: 220,
  // methods
  accelerate: function(value) {
    if (this.currentSpeed + value <= this.maxSpeed)
      this.currentSpeed += value;
  },
  brake: function(value) {
    if (value && this.currentSpeed - value >= 0)
      this.currentSpeed -= value;
    else
      this.currentSpeed = 0;
  }
};

Okay, now we are dealing with a slightly different object: it has properties and methods. What differs a method from a property is that its value is a function. That is, methods perform actions, being able to modify the properties of the object to which they belong.

In our example, the method accelerate accesses the properties currentSpeed and maxSpeed, checks whether the car's speed can be increased and, if so, changes the value of the currentSpeed, adding the value we pass as an argument.

In practice, we could use the code above as follows to test the commands:

car1.accelerate(20); // Change speed to 20
car1.accelerate(25); // Change speed to 45
car1.brake(10); // Change speed to 35

this

Now, pay attention to one detail:

if (this.currentSpeed + value <= this.maxSpeed)
  this.currentSpeed += value;

Why do we use the keyword this before the property name we want to read or change?

Properties and methods are dependent on the object to which they belong. So, if we just write currentSpeed, our interpreter would look for a variable with this name instead of the property inside the object. This means that we must always use the following syntax to read properties from within the object:

this.propertyName;

But, to read a property of the object outside it, we use the syntax:

objectName.propertyName;

What are the advantages of object-oriented code?

The advantage starts with the fact that almost everything in JS is an object, which includes the global scope where we write our code. Take an example:

console.log('Some text here');
this.console.log('Another text here');

If you run this code and open the console, the two lines will appear, including the one with the keyword this before the reference to console.

By the end of this reading, you will understand two essential factors for more comfortable programming: readability and automation.

But the advantages become noticeable as our code increases. In the car game example, we saw that controlling the car's properties from inside the object is very effective. But what if you had to create 10 cars? Then the code would be quite extensive, without even considering that it would be easy to make mistakes, such as misspelling the name of a property or method.

How can we get around this situation?

Classes

Classes allow us to create an object model that will represent a specific entity in our code.

class Car {
  constructor(name) {
    this.name = name;
    this.currentSpeed = 0;
    this.maxSpeed = 220;
  }

  accelerate(value) {
    if (this.currentSpeed + value <= this.maxSpeed)
      this.currentSpeed += value;
  }

  brake(value) {
    if (value && this.currentSpeed - value >= 0)
      this.currentSpeed -= value;
    else
      this.currentSpeed = 0;
  }
}

Let's look at the code above. Instead of creating the properties directly, we define them within a constructor function. The constructor fires once the instance is created. That is, when we create a new car using this class, the code inside constructor will be executed. This means that it is there that we must define the properties that will be part of our object and their respective values.

It is also possible to define parameters in constructor:

constructor(name) {
  this.name = name;

Now, if we want to create a new car, just invoke our class using the keyword new:

const car1 = new Car('Car 1');

We can change the object's values ​​or use its methods, as follows:

car1.name = 'Another name';
car1.accelerate(20);

See how easy it is to create as many instances of the class Car as needed:

const car2 = new Car('Car 2');
const car3 = new Car('Car 3');

Remember that huge code we wrote at the beginning to create a car? Now it has been reduced to just one line!

Wow!

We can modify other properties in our constructor. Every cool racing game has cars that reach different speeds. Shall we leave ours like that too?

It is possible to let the maximum speed be customized and guarantee it a default value when no other is passed. Look:

constructor(name, maxSpeed) {
  this.name = name;
  this.maxSpeed = maxSpeed || 220;

Our cars can now be created this way:

const car1 = new Car('Slow car', 80);
const car2 = new Car('Default car');
const car3 = new Car('Fast car', 300);

The maximum speeds of these cars will be, respectively: 80, 220, and 300.

What does it mean to be a car?

Classes will always represent entities. So, when creating a class - in this case, for a car -, you must think about these issues:

  • What does it mean to be a car?
  • What does a car have?
  • What does a car do?

The properties and methods of the class will be instantiated in all objects created by it. So just put in the class Car what all the cars in the game will have.

In some cases, however, you will have cars with the same properties and methods as the class Car, but with a few more features.

For example, let's assume that some cars have the turbo option, which makes the car go straight to its maximum speed. These cars will continue to have the same properties and methods as the others, however, they will also have the method turbo. And now? How do we solve this?

Extends

To create cars with the turbo option, we can create a new class that will extend Car:

class Turbo extends Car {
  turbo() {
    this.currentSpeed = this.maxSpeed;
  }
}

How about creating a new car with the turbo option?

const car = new Turbo('My fast car', 320);
car.turbo();
car.brake(20);

The current speed of car is 300.

Both cars created directly with the command new Car() and those created with new Turbo() are instances of Car. This is because Car is the parent class of Turbo. That is, regardless of how the instances are created, all objects will represent cars. That is why we can access the properties of Car within Turbo.

Conclusion

Writing object-oriented code can bring more convenience to the developer, because:

  • The code is more organized since we declare properties and methods within the scopes where they will be used, that is, their classes. This makes it much easier to find the part of the code that needs to be edited.
  • The reduction in the number of lines of code is clearly noticeable since we can instantiate classes with just one line of code.
  • Instead of creating various functions and passing our objects as parameters to them, we make the object's content modifying actions occur within itself. In this way, it is the objects that orchestrate the code, without requiring calls from functions external to them to solve their problems.

It is worth remembering that classes, in the syntax we saw here, are a new feature of JS, which means that many browsers still don't support them. To work around this, you can install Node packages that adapt your code for browsers. One is Browserify.

I hope you enjoyed this brief explanation! πŸ˜„

Buy me a coffee