Sunday, September 26, 2021

OOP in JavaScript with Classes

JavaScript (JS) is a wonderfully quirky language. Inspired by Smalltalk, it uses a C-like syntax that combines aspects of procedural, functional, and object-oriented programming (OOP), all thrown together in a delightfully non-opinionated way. JavaScript is object-oriented in the sense that everything in JS is an object. However, JS objects are not the same as the ones you’ll find in Java or C#. One major difference is that JS employs prototypical inheritance, which is a world away from the inheritance mechanisms of traditional OO languages.

Many people have written libraries that add syntactic sugar to the JS language to mask the prototypical aspect of its objects, but it wasn’t until 2015 that JavaScript introduced the class keyword with the release of ES6. Since then, developers have been gleefully rejoicing that JavaScript is a real object-oriented language at last! Not so fast. Under the covers, the language still works in exactly the same way. Nonetheless, the introduction of classes does provide developers a new option to create objects using a syntax that they are familiar with. This blog will demonstrate how to define and work with the new ES6 classes.

Our First ES6 Class

Here’s the definition for a Vehicle class:

class Vehicle {

  constructor(vin) {
    this._vin = vin;
  }

}

The constructor() method is utilized to create and initialize our class. Technically, it’s not required; omitting it will use the default constructor sans parameters. That being said, they are an ideal way to initialize class member variables, like the vin number. If you do include a constructor, you have to pass all initialization parameters to it. Don’t try to override it for various parameter combinations, as having more than one constructor will throw a SyntaxError. There’s no reason to do so anyway, since JS doesn’t enforce the number or types of parameters passed.

To create an instance, call the class with the new keyword, passing in whatever initialization parameters you’d like to set during object creation:

const vehicle = new Vehicle('1HGBH41JXMN109186');
console.log(vehicle); //Vehicle { _vin: '1HGBH41JXMN109186' }

Getters and Setters

Also known as Accessor and Mutator methods, Getters and Setters hide the internal representation of class properties while exposing them to outside actors using an alternative representation. Take a look at this expanded version of the Vehicle class that includes getters and setters, as well as a couple of methods, just for good measure:

class Vehicle {
  _speed = 0;

  constructor(vin, maxSpeed, accelFactor) {
    this._vin = vin;
    this._maxSpeed = maxSpeed || 160;
    this._accelFactor = accelFactor || 10;
  }

  get vin() {
    return this._vin.toUpperCase();
  }

  get maxSpeed() {
    return this._maxSpeed;
  }

  set maxSpeed(maxSpeed) {
    this._maxSpeed = maxSpeed;
  }

  get accelFactor() {
    return this._accelFactor;
  }

  set accelFactor(accelFactor) {
    this._accelFactor = accelFactor;
  }

  get speed() {
    return this._speed;
  }

  accelerate() {
    if (this._speed <= this._maxSpeed - this._accelFactor) {
      this._speed += this._accelFactor;
    }
  }

  decelerate() {
    if (this._speed >= this._accelFactor) {
      this._speed -= this._accelFactor;
    }
  }
}

const vehicle = new Vehicle('1HGBH41JXMN109186', 150, 20);
console.log(vehicle.vin); //1HGBH41JXMN109186
console.log(vehicle.maxSpeed); //150
console.log(vehicle.accelFactor); //20
console.log(vehicle.speed); //0
vehicle.accelerate();
vehicle.accelerate();
console.log(vehicle.speed); //40
vehicle.decelerate();
console.log(vehicle.speed); //20

Although, our Vehicle class accepts up to three constructor parameters, there is only one member variable declared (the _speed). That’s because constructor parameters can be assigned directly to the this pointer. Otherwise, you’ll have to declare it before you can access it.

Static Methods

A static method is one that may be invoked on the class itself, rather than on a class instance. To demonstrate, here’s a method that checks if the vehicle is traveling over the speed limit. Since the method accepts everything it needs to make a determination, namely the speed and limit, it may be implemented as static. This allows us to invoke it directly on the Vehicle class as follows:

static isOverSpeedLimit(speed, limit) {
  return speed >= limit;
}

// ... 
console.log(Vehicle.isOverSpeedLimit(120, 100)); //true

Subclassing with Extends

One of the most important features of classes is their ability to extend another in order to alter or enhance the parent’s functionality. Thus, the extends keyword is used in class declarations to create a class as a child of another class. The Car class below extends Vehicle and includes the _numberOfDoors attribute because vehicles such as dune buggies, golf carts, and tractors do not necessarily have doors at all!

class Car extends Vehicle {
  constructor(vin, maxSpeed, accelFactor, numberOfDoors) {
    super(vin, maxSpeed, accelFactor);
    this._numberOfDoors = numberOfDoors;
  }

  get numberOfDoors() {
    return this._numberOfDoors;
  }
}

const myCar = new Car('1HGBH41JXMN109186', 150, 20, 2);
console.log(myCar.vin);
console.log(myCar.maxSpeed);
console.log(myCar.accelFactor);
console.log(myCar.accelFactor);
console.log(myCar.numberOfDoors);
myCar.accelerate();
myCar.accelerate();
console.log(myCar.speed);
myCar.decelerate();
console.log(myCar.speed);

The Car’s constructor accepts four parameters, so that it can pass along the first three values along to the Vehicle’s constructor via the call to super().

Browser Support

Since the 2015 release of ES6, there have been a few upgrades, but the bulk of the new features, including let/const, default parameter values, as well as some new Array methods were part of the first version of ES6. It’s supported by the following browsers:

  • Chrome 51
  • Firefox 52
  • Edge 14
  • Safari 10
  • Opera 38
  • MS Edge 14

The only popular browser that does not support ES6 is Internet Explorer. Microsoft are no longer supporting it, so it won’t be long before there are virtually no users of this outdated relic.

You’ll find a demo with today’s code on Codepen.io.

Conclusion

Some have argued that it’s better to focus your efforts on learning all about Prototypal inheritance than introducing more syntactic sugar to your code. I, on the other hand, take a more liberal stance, and feel that your particular development needs should dictate whether or not you take advantage of the new ES6 class offerings. As a long-time Java developer, it certainly was a welcome addition to the language for me!

Rob Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Popular Articles

Featured