beginner

Prototype-Oriented Programming(POP)


Prototype-Oriented Programming (POP) is a style of object-oriented programming in which behaviour reuse (known as inheritance) is performed via a process of reusing existing objects that serve as prototypes. What some people don’t know is that JavaScript is inherently prototype-based.

Understanding Prototype-Oriented Programming

In POP, objects are created as instances of other objects known as prototypes. These prototypes act as templates, and objects inherit properties and methods from their prototypes. This approach provides a dynamic way to extend objects without the need for explicit class definitions.

Creating Objects with Prototypes

Let’s see a basic example of creating objects using prototypes:

const animalPrototype = {
  sound: "",
  makeSound() {
    console.log(this.sound);
  },
};

const cat = Object.create(animalPrototype);
cat.sound = "Meow";

const dog = Object.create(animalPrototype);
dog.sound = "Woof";

cat.makeSound(); // Output: Meow
dog.makeSound(); // Output: Woof
animalPrototype.makeSound(); // ""

In this example, both cat and dog objects are created by inheriting properties and methods from the animalPrototype.

You might be confused as to why we can`t just do const cat = animalPrototype and instead we need Object.create. If we did it like that we would overwrite the “Meow” with “Woof” because its passed by reference rather than by value(it just means the object is not copied and instead it is using the same animalPrototype in the background).

const animalPrototype = {
  sound: "",
  makeSound() {
    console.log(this.sound);
  },
};

const cat1 = animalPrototype;
cat1.sound = "Meow"; // we are changing the original sound that is "" up until now

cat1.makeSound(); // Meow

const dog1 = animalPrototype;
dog1.sound = "Woof"; // we are changing the original sound that was first "" and then was "Meow" up until now

cat1.makeSound(); // Woof
dog1.makeSound(); // Woof
animalPrototype.makeSound(); // Woof

Prototype Chain

This part is a bit more intermediate and I will explain more in later posts and for now I want to keep it as simple as possible.

Every object in JavaScript has a built-in “hidden” property, which is called its prototype. The prototype is itself an object, so the prototype will have its own prototype, making what’s called a prototype chain. The chain ends when we reach a prototype that has null for its own prototype. (if you console.log(animalPrototype) in your browser console you will see it as [[Prototype]]).

Prototype chain is used to find a method or a property up the chain if until it finds it or until it goes to null prototype.

const a = {};
console.log(a.toString()); // [object Object]

In the above example we have an empty object a. But how can we call .toString()? Because it is searching up the prototype chain and it find it in “base” Object. This is how it looks in the background:

a->base->null

Let`s make our own prototypes.

const animal = {
  type: null,
  color: null,
  weight: null,
  getType() {
    return this.type;
  },
  eat() {
    console.log("Eating...");
  },
};

const mammal = {
  type: "mammal",
  hasHair: true,
  hasMilk: true,
};

const reptile = {
  type: "reptile",
  isColdBlooded: true,
  laysEggs: true,
};

const fish = {
  type: "fish",
  hasScales: true,
};

Object.setPrototypeOf(mammal, animal); // Set prototype of mammal to be animal
Object.setPrototypeOf(reptile, animal);
Object.setPrototypeOf(fish, animal);

const dog = {
  weight: 25,
  color: "golden",
};

const snake = {
  weight: 2,
  color: "green",
};

const shark = {
  weight: 200,
  color: "gray",
};

Object.setPrototypeOf(dog, mammal); // Set prototype of dog to be mammal
Object.setPrototypeOf(snake, reptile);
Object.setPrototypeOf(shark, fish);

console.log(shark.weight); // 200
console.log(shark.hasMilk); // undefined
console.log(shark.hasScales); // true
console.log(shark.getType()); // fish

Our prototype chains in the above example look like

dog->mammal->animal->base->null
shark>fish->animal->base->null
snake>reptile->animal->base->null

Caveats to Be Aware Of

While Prototype-Oriented Programming can simplify code and promote dynamic object creation, it also comes with certain caveats:

  1. Shared State: Since objects share the same prototype, changes made to the prototype can affect all instances.
const animalPrototype = {
  sound: "",
  makeSound() {
    console.log(this.sound);
  },
};

const cat = Object.create(animalPrototype);
cat.sound = "Meow";

const dog = Object.create(animalPrototype);
dog.sound = "Woof";

animalPrototype.eat = function () {
  console.log("Eating...");
};

cat.makeSound(); // Output: Meow
dog.makeSound(); // Output: Woof
cat.eat(); // Eating...
dog.eat(); // Eating...
delete animalPrototype.makeSound; // We delete the existing "parent" function
cat.test(); // Trows error because this no longer exists
  1. Prototype Chain: Accessing properties deep in the prototype chain can lead to performance issues due to property lookup.

  2. Lack of Structure: The absence of class definitions might make code organization less intuitive for developers accustomed to class-based OOP.

What now?

Now you know what Javascript is based on and that is huge for your further development and understanding of Javascript and NodeJS. But just reading is not enough, you need to experience it yourself. Think about how you would create multiple Vehicle prototypes/classes. What different types of vehicles exist? Use the animal code example as reference point.

Previous Asynchronous JS: Callbacks, Promises and Async/Await
Next Object-Oriented Programming(OOP)