Object Oriented Javascript

A post from Oleg Yanchinskiy

Object Oriented Javascript is often difficult for both beginner and experiences engineers. Pre ES6 Javascript inheritance works differently from classical oop languages, there's definitely a few nuances that engineers of all levels get caught up when trying to implement object oriented patterns.

Javascript uses prototypical inheritance - this is the mechanism by which Javascript objects inherit properties and methods from one another.

So what is object-oriented programming?
In one sentence: Object-oriented programming is the use of objects to model real world things that we want to represent inside of our application.

Pre ES6 Javascript versions don't have a class keyword, so we've become accustomed to using functions to create new classes. These functions that we use to build new objects are called constructor. If we are using ES6 we can use the class keyword for creating new classes.

ES 2015

// Vehicle class constructor function
function Vehicle(type, doors, color){
  this.type = type;
  this.doors = doors;
  this.color = color;
}

// adding a drive method to the vehicle prototype
Vehicle.prototype.drive = function(){
  return "Driving my " + this.type;
}

ES6

class Vehicle extends Object {
  // create a constructor class
  constructor(type, doors, color) {
    // call super so we can use this keyword  
	super();
	this.type = type;
	this.doors = doors;
	this.color = color;
  }
  // add drive to the Vehicle prototye
  drive(){
    return "Driving my "  + this.type;
  }
}

We can instantiate a new instance of our class using the new keyword. There are 4 things that happen when we use new to create a new instance of the Vehicle constructor.

  1. A new object is created.
  2. We set the constructor property of the object to Vehicle.
  3. We set the prototype to delegate to Vehicle.prototype
  4. We call the Vehicle constructor with a new this.
const whiteLimo = new Vehicle('limo', 6, 'white')'
whiteLimo.drive() // "driving my limo" looks at the Vehicle prototype to call the drive method
whiteLimo.type // "limo"
whiteLimo.doors // 6

Classes can have also have private variables and functions that are only accessible by privileged methods. Privileged methods are not attached to the prototype and are created at every instance of the class. Therefore they take more memory, and should be limited in number.

function Vehicle(type, doors, color, vin){
  // public properties
  this.type = type;
  this.doors = doors;
  this.color = color;
  // private variable
  var vin = vin + this.type;
  // public methods (non prototype)
  this.getVin = function(){
    return vin;
  }
  this.setVin = function(vin){
    vin = vin + this.type;
    return vin;
  }
}

const mySedan = new Vehicle('sedan', 4, "blue", "abc123")

mySedan.vin  // undefined
mySedan.getVin() // "abc123sedan"
mySedan.setVin("apples42")//  "apples42sedan" 
mySedan.constructor == Vehicle // true
mySedan instanceof Vehicle // true 

Creating subclasses is extremely important for writing efficient code and following the dry principles.

To create a subclass follow the following recipe -

  1. Create a constructor function for your subclass
  2. Use .call() to set the context of the parent class as this class
  3. Set subclass prototype to a copy of the parent prototype
  4. Set subclass prototype constructor to subclass constructor

Let's take a closer look at this in practice. If we want to create a Car class that extends the Vehicle class, this is how we would do it.

// ES 2015 
function Car(type, doors, color, vin, make, model){
  Vehicle.call(this, type, doors, color, vin);
  this.make = make;
  this.model = model;
  this.speed = 0;
}

Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car;

After that we can use methods on the Vehicle prototype as well as add other methods to the Car class.

Car.prototype.honk = function(){
  return "honking - move over!";
}

Car.prototype.increaseSpeed = function(){
  return this.speed += 10;
}

var myHonda = new Car("sedan", 4, "black", "1235abc", "honda", "civic");

myHonda.getVin() // "1234abcsedan"  looks at the Vehicle prototype
myHonda.honk() // "honking - move over!" looks at the Car prototype
myHonda.increaseSpeed() // 10
myHonda.increaseSpeed() // 20

myHonda instanceof Vehicle // true
myHonda instanceof Car // true 

ES6 syntax is similar to what we did above

class Car extends Vehicle {
  constructor(type, doors, color, make, model){
   // call super to set context of this
   super(type, doors, color);
   this.make = make;
   this.model = model;
 }

  honk(){
    return "ES6 car honking";
  }

  removeDoor(){
    return --this.doors;
  }

  addDoor(){
    return ++this.doors;
  }
}

let myHonda = new Car("sedan", 4, "black", "1235abc", "honda", "civic");

myHonda.addDoor() // 5
myHonda.honk() // "ES6 car honking"
myHonda instanceof Car // true
myHonda instanceof Vehicle // true

For my last example I'll be showing a shortcut for creating sub-classes with certain properties already set. The key to this is Javascript's bind method. When a bound function is used to construct a value, the provided this is ignored, however, provided arguments are still prepended to the constructor call. Let's take a look at what this looks like.

var Coupe = Car.bind(null, 'coupe', 4); // binding 'coupe' and 4 (doors) to the Car constructor, null is ignored
Coupe.prototype = Object.create(Car.prototype);
Coupe.prototype.constructor = Coupe;

var myCoupe = new Coupe('yellow', 'coupecoupe2', "bmw", "335is");
myCoupe instanceof Coupe // true
myCoupe instanceof Car // true
myCoupe instanceof Vehicle // true
myCoupe instanceof Object // true

There's a lot more information on ES6 classes that I want to cover in a future post. Prior to ES6 creating classes in Javascript always felt a little hacky but the new features provide the same capabilities as full fledged object oriented languages such as Java and Python.

bleak theme by Jack Preston