Object-Oriented Programming in JavaScript. Basics, definitions, and examples of design patterns.

Irina Mityugova
6 min readJun 16, 2023

--

This is Tom. What’s his prototype?

As a new concept, OOP in JavaScript can be a challenge to put your mind around. It took me over two months of procrastination to feel a good grasp of abstractions and their implementations. In this article, I will describe:

  1. OOP design patterns,
  2. Tools available to craft these designs, and
  3. Examples of their implementation.

OOP designs encapsulate states and functionalities, provide a public interface and minimize duplication through inheritance. These designs help programs easier to debug and maintain. Even though proper encapsulation may reduce dependencies, some cases may not lead to more efficient code as they may take more memory space.

  • Encapsulation — bundling the state (properties) and behavior (methods) into an object.
  • Public interface — accessible functionalities and data of objects available for use without exposing the implementation details.
    Ex: length property of the Array.prototype returns the length of an array when called upon an instance like ['a', 'b', 'c'].length //3
  • Inheritance — delegating objects' method calls and property look-up to the prototype chain. For example, array instances inherit from Array.prototype. That’s why a method ['z', 'b', 'c'].sort() will be delegated to Array.prototype.
    Ex: ['a', 'b', 'c'].hasOwnProperty(1) //true
  • Dependencies — When manipulations of one section impact another section of the program. In other words, when one part, or function, depends on another. This causes a domino effect when editing or debugging, making code vulnerable to errors.

OLOO — Objects Linking Other Objects

Regular factory functions are great for creating many objects of the same type; however, some methods and static properties endure a significant amount of duplication in the background. OLOO solves the concern by making use of prototypes and, instead of duplicating, delegating methods and properties up the prototype chain.

  • Factory function— a function that returns an object literal. It is useful when creating multiple objects with the same properties. The downside to this pattern is a duplication of methods in the background nor can we tell the “type” of the created object. that can be avoided using prototypal inheritance.
  • Prototypal inheritance — an inheritance where the internal [[Prototype]] property is set to another object, allowing the inheriting object to access properties and methods. Note that, in JavaScript, an object can inherit only from one prototype.
In JavaScript, an object can inherit only from one prototype.

A convenient way to set up the OLOO pattern is to

  1. Create an object literal as a prototype
  2. Define an initializer method init for instance properties
  3. Use Object.create(proto) to create an instance of the object
let catPrototype = {
type: 'cat',
speak: function () {},
init: function (name, age) {
this.name = name;
this.age = age;
return this;
},
};

let tom = Object.create(catPrototype).init('Tom', 3);
// Object.create creates a new instance of catPrototype,
// .init assigns instance properties and returns the object
// an instance gets assigned to a variable tom

let fury = Object.create(catPrototype).init('Fury', 1);
// Object.create creates a new instance of catPrototype,
// .init assigns instance properties and returns the object
// an instance gets assigned to a variable fury

Pseudo-Classical Design using Constructors and Inheritance

JavaScript does not have a Class as a separate data type. When JavaScript developers refer to a class, they are referring to a constructor function. Hence the name — pseudo-classical.

Pseudo-classical inheritance uses constructors’ built-in prototype property that references an object. When the constructor is invoked, the returned instance has its internal [[Prototype]] property set to reference the same object referenced by the constructor’s prototype property.

The trick here is not to equate object prototype with constructor prototype property:

  • [[Prototype]] or prototype—a reference to the parent, or supertype, from which an object can inherit methods and properties. In JavaScript, an object can inherit only from one prototype.
  • Constructor — a function that is designed to return an instance using the invocation with a new keyword.
  • Instance — an object created by the constructor.
  • Constructor prototype property —a built-in property of a constructor that references the prototype of its instances.
The constructor’s prototype property IS NOT its own prototype, it intends to become an internal [[Prototype]] of its instances. Functions are also objects. That’s why the constructor function `Cat` also has a prototype of the `Function` prototype.

No worries, it’s simple. I’ll repeat it a few times until it’s clear. Let’s do an example,

function Cat (name, age) {
this.name = name;
this.age = age;
}

Cat.prototype.speak = function () {
return 'Myow!';
};

let tom = new Cat('Tom', 3);
let fury = new Cat('Fury', 1);

The constructor’s .prototype property IS NOT its prototype, it is a [[Prototype]] of its instances.

To prove the point, here are some logs that use the previous code. Feel free to run them in your browser or REPL.

// The constructor property refers to the function that created the object
console.log(Cat.constructor); // Function
console.log(tom.constructor); // Cat

// `type` property belongs to the prototype of `tom` but not of `Cat`
// Constructor DOES NOT inherit from its `.prototype` property
// The instance of the constructor inherits from the constructor's prototype
console.log(tom.sound()); // 'Myow!'
console.log(Cat.sound()); // TypeError: Cat.speak is not a function

// Constructor Cat's prototype property IS NOT its prototype,
// Its [[Prototype]] is Function.prototype
console.log(Cat.prototype); // {speak: function(){}}
console.log(Object.getPrototypeOf(Cat) === Function.prototype); // true

// The `type` property is in a hidden [[Prototype]] of an instance
// It can be accessed by a dunder proto (depricated) or Object.getPrototypeOf()
console.log(tom.prototype); // undefined
console.log(tom.__proto__); // {speak: function(){}}
console.log(Object.getPrototypeOf(tom)); // {speak: function(){}}

console.log(Cat.prototype === Object.getPrototypeOf(Cat)); // false
console.log(Cat.prototype === Object.getPrototypeOf(tom)); // true

Why the drama or How to add a subtype?

Using the previous code, let’s add a subtype by linking the prototype of all instances to the prototype of the supertype:

function Cheshire (name) {
// reusing Cat constructor for the same properties (lazy or smart?)
Cat.call(this, name, 'immortal');
}

console.log(Cheshire.prototype.constructor); // Cheshire

Cheshire.prototype = Object.create(Cat.prototype);

let cheshire = new Cheshire ('Cheshire');
console.log(cheshire.sound()) // 'Myow!';

// Prototype chain would look like this:
// cheshire ---> Cheshire.prototype ---> Cat.prototype ---> Object.prototype ---> null

In Object.create(prototype) , the argument is a prototype of a newly created object. A side effect of the method is that the default constructor property Cheshire.prototype.constructor is overridden to Cat; however, Cheshire is the actual constructor that we use to create instances. It’s important to reset the constructor to Cheshire for the correct, specific reference. For instance, how would a Cheshire breeder feel if you say that she just breeds Cats? Be specific, and no feelings would get hurt.

Best practice: Good labels make our lives easier.

console.log(Cheshire.prototype.constructor); // Cat
Cheshire.prototype.constructor = Cheshire;
Prototype chain: cheshire — > Cheshire.prototype — > Cat.prototype —> Object.prototype

ES6 Class Sugarcoating

class is sweet. It’s a constructor function with a little blemish.

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

Cat.prototype.sound = function () {
return 'Myow!';
}

class Cheshire extends Cat {
constructor(name) {
super(name, 'immortal');
}
}

let tom = new Cat('Tom', 3);
let cheshire = new Cheshire('Cheshire');
  • No commas!
  • Instance state properties go to the constructor method within the class
  • Static properties (on the constructor object) need a keyword static
  • Superclass is assigned with extends keyword
  • Call super to invoke the supertype constructor or access its arguments

It’s scrumptious! Do I need to say anything else?

Lil Extra on Dependencies

Found it a struggle myself, to understand where to put what methods and properties. If you’re having a hard time with classes and you’re hack-slashing (or copy-pasting) to try to make the RPS, TTT, or 21 games work, take an abstraction step back to see what dependencies you have.

Encapsulation

— Are the interactions and properties making sense?

— Do these functionalities belong to a subclass or superclass?

Verbs and nouns draft in the beginning help a lot. I found it helpful to think in the following sequence: who is who, who has what, who does what, do does how. It’s very important not to get bogged down by the details of implementation before the structure is complete. Plan first, code last.

Mix-ins

— Do unrelated classes sometimes share functionality?

If two things are unrelated, don’t weave a thread through method arguments to get to a method in a sub-sub-sub class of a sister branch in inheritance. Pull the method out into a mix-in, and conveniently sprinkle it onto a Christmas cake and a car wrap design.

Functional or Object-Oriented Programming

— Did the methods in your code get really lengthy?

A function or a method should do one thing, be it a side-effect or an output.

Questions/Comments? Slack me in LS

--

--

Irina Mityugova

Graphic Designer, transitioning to Software Development with Launch School