Classes in Javascript

Jon SY Chan
Dec 2 · 5 min read

When I first started diving into Javascript I realized this language had many different ways to do the same thing. In fact since it’s always growing with new methods and syntax all the different ways Object Oriented Programming is represented in Javascript can get confusing.

The basic idea of OOP is that we use objects to model real world things that we want to represent inside our programs, and/or provide a simple way to access functionality that would otherwise be hard or impossible to make use of.

Objects can contain related data and code, which represent information about the thing you are trying to model, and functionality or behavior that you want it to have. Object data (and often, functions too) can be stored neatly (the official word is encapsulated) inside an object package (which can be given a specific name to refer to, which is sometimes called a namespace), making it easy to structure and access; objects are also commonly used as data stores that can be easily sent across the network.

https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_JS

First off, I’d like to make it clear that Classes in Javascript (introduced with ES6 — June 2015) is just a syntactical representation of classes. Under the hood it is still run with constructor functions and objects. This was originally introduced to make things easier for people coming from a different language where there were Classes.

The thing that confused me the most when I was learning JS was seeing multiple different ways of writing the same code. This was partly due to a time period where there were just so many ways to do it. In this blog I will explain a couple different ways to write classes in Javascript.

Object Literal

const dog1 = {
name: 'oreo',
age: 2,
size: 'small',

getInfo: function() {
console.log(`Name: ${this.name}, Age: ${this.age}, Size: ${this.size}`)
}
}
dog.getInfo() logs -> Name: oreo, Age: 2, Size: small

This method to represent OOP can be redundant because if you wanted to make another dog with the same format you’d have to copy and paste code all over again. This leads into the constructor method.

This article helps lay out the differences of constructor functions and object literals.

Constructor Functions and Prototypes — ES5

With the constructor function notation you can instantiate multiple instances with the ‘new’ keyword.

Constructor functions technically are regular functions. There are two conventions though:

They are named with capital letter first.

They should be executed only with "new" operator.

For instance:

function User(name) {
this.name = name;
this.isAdmin = false;
}

let user = new User("Jack");

alert(user.name); // Jack
alert(user.isAdmin); // false

When a function is executed with new, it does the following steps:

A new empty object is created and assigned to this.

The function body executes. Usually it modifies this, adds new properties to it.

The value of this is returned.

In other words, new User(...) does something like:

function User(name) {
// this = {}; (implicitly)

// add properties to this
this.name = name;
this.isAdmin = false;

// return this; (implicitly)
}

So let user = new User("Jack") gives the same result as:

let user = {
name: "Jack",
isAdmin: false
};

https://javascript.info/constructor-new

function Dog(name, age, size) {
this.name = name;
this.age = age;
this.size = size;
this.getInfo: function() {
return `Name: ${this.name}, Age: ${this.age}, Size: ${this.size}`
};
}const dog1 = new Dog('oreo', 2, 'small')
const dog2 = new Dog('cookie', 3, 'medium')

This allows you to create as many dogs as you want with that constructor function.

The issue with the this.getInfo method being in the constructor function is that now all your dogs that are instantiated will have redundant code. This leads into the prototype method.

function Dog(name, age, size) {
this.name = name;
this.age = age;
this.size = size;
}
Dog.prototype.getInfo = function() {
return `Name: ${this.name}, Age: ${this.age}, Size: ${this.size}`
}
const dog1 = new Dog('oreo', 2, 'small')
const dog2 = new Dog('cookie', 3, 'medium')

All javascript objects inherit properties and methods from a prototype. This is implemented through the prototype chain.

Inheritance with ES5

//Subclass 
function Poodle(name, age, size, color) {
Dog.call(this, name, age, size);
this.color = color;
}
// Inherit Prototype of Dog
Poodle.prototype = Object.create(Dog.prototype);

the prototype of new objects created with the Circle constructor is an object whose prototype is the prototype of objects created by Shape constructor. Yeah, that’s a handful. But it can be simplified as: each Circle has a Shape as its prototype.

https://eli.thegreenplace.net/2013/10/22/classical-inheritance-in-javascript-es5

// Use Poodle Constructor
Poodle.prototype.constructor = Poodle;

While not strictly necessary, setting constructor to the poodle constructor is set to preserve some useful invariants. Since the assignment to Circle.prototype kills the existing Circle.prototype.constructor (which was set to Circle when the Circle constructor was created), we restore it.

https://eli.thegreenplace.net/2013/10/22/classical-inheritance-in-javascript-es5

const poodle1 = new Poodle('bacon', 4, 'big', 'white');// this works!
poodle1.getInfo()

Object Create

// Factory Functionsconst dogProtos = {
getInfo: function() {
return `Name: ${this.name}, Age: ${this.age}, Size: ${this.size}`
}
}
// Option 1
const dog1 = Object.create(dogProtos)
dog1.name = 'oreo'
dog1.age = 2
dog1.size = 'small'
// Option 2
const book1 = Object.create(dogProtos, {
name: {value: 'oreo'},
age: {value: 2},
size: {value: 'small'}
})

Classes-ES6

Under the hood classes that were introduced in ES6 are doing the same thing as the ES5 techniques. Classes provide a visually cleaner and more organized way to write that code.

class Dog {
constructor(name, age, size) {
this.name = name;
this.age = age;
this.size = size;
}

getInfo() {
return `Name: ${this.name}, Age: ${this.age}, Size: ${this.size}`
};
// a static function doesn't need to be instantiated. needs to be
// run on the actual class
// Can be run with Dog.simpleDef()
static simpleDef() {
return 'a domesticated carnivorous mammal that typically has a long snout, an acute sense of smell, nonretractable claws, and a barking, howling, or whining voice.'
}
}// Instantiate Object;
const dog1 = new Dog('oreo', 2, 'small');

Subclasses with ES6

In place of the .call method Classes use super() to access the parent class.

class Poodle extends Dog {
constructor(name, age, size, color) {
super(name, age, size);
this.color = color
}
}
const dog2 = new Poodle('cookie', 4, 'big', 'white');// Works!
dog2.getInfo()

It should be noted that it is not required explicitly to define a constructor function for classes and a default constructor will be generated if not provided.

In this blog I didn’t dive too deep into the prototype chain. It is important to notice that there is both a .prototype and a .__proto__ on these class objects. Arnav’s blog here goes into it.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade