The joy of prototypes

Dare prototypes. It’s a powerful feature of the language and perhaps the key to the next level.

Let’s see why and how to prevent common mistakes.

Disclaimer

I use the term “methods”, but I should use “properties” instead.

What is it?

Each object in JavaScript has a prototype, a private property that links to another object.

Each object inherits properties and methods from a prototype.

The prototype chain

You can call it the inheritance chain too.

In JavaScript, you have Object at the top of the chain.

Under Object, objects and their prototypes, and prototypes of the prototypes until it hits null (the end):

Object => someObject => prototype => prototype => ... => null

Everything is an object in JavaScript. The prototype chain applies everywhere, including functions and arrays.

That’s why you can use sort or filter on all arrays directly. It’s the native Array prototype that provides them.

Simple example

function Game(elements) {
  this.elements = elements;
}

Game.prototype.shuffle = function () {
  let size = this.elements.length;
  for (let i = size - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [this.elements[i], this.elements[j]] = [this.elements[j], this.elements[i]];
  }
}

let game = new Game(["rock", "paper", "scissors"]);
game.shuffle();
console.log(game.elements[0]);// refresh the page to shuffle

The function object above takes an array and shuffles it. I use the new keyword to create a constructor automatically.

Don’t move the shuffle() method inside the constructor, even if you think about a factory pattern.

It’s possible, but you would add one shuffle() method for each instance. Every property you add to your objects consumes memory.

The shuffle() method is more like a static function. If you copy it on each instance, it’s inefficient.

Let’s try with the prototype:

let game = new Game(["rock", "paper", "scissors"]);
let game2 = new Game(["", "", "", "", "", "", "bullet"]);
game.shuffle();
game2.shuffle();//russian roulette ^^
console.log(game.elements[0], game2.elements[0]);
console.log(game.shuffle === game2.shuffle);// true

It’s one shuffle() method only for all instances, no unnecessary copy, no extra memory consumption.

N.B.: Game is a constructor function. The new keyword allows for constructing an object and returning an object’s instance. Because Game does not return anything, JavaScript automatically inserts return this; at the end.

Don’t touch native prototypes

JavaScript is a prototype-based language, but they have a name for the act of modifying native prototypes: monkey patching.

Don’t do the following:

Array.prototype.shuffle  = function () {}

While there can be a good reason for this monkey patching, it’s highly infrequent, so please don’t do it.

Don’t use __proto__

__proto__ is an accessor property that exposes an object’s internal prototype.

Please don’t do the following:

MyObject.__proto__ = function() {};

While it’s possible, it’s a shitty practice because it breaks the browser’s optimizations with prototypes.

Likewise, using Object.setPrototypeOf() is not recommended.

What is the purpose of hasOwnProperty()?

If you look for something high in the chain, it can harm performance. Any attempt to get a property that does not exists results in a full chain lookup.

Fortunately, JavaScript has a built-in method you can use to check whether a property is inherited or not:

function Game(elements) {
  this.elements = elements;
}
let game = new Game(["rock", "paper", "scissors"]);
console.log(game.hasOwnProperty("elements"));// true
console.log(game.hasOwnProperty("hasOwnProperty"));// false

Dodge this

It is always the same order. JavaScript looks first in the constructor then in the prototype:

function Game(elements) {
  this.elements = elements;
}
Game.prototype.name = "Russian Roulette";
Game.prototype.elements = ["", "", "", "", "", "", "bullet"]
let game = new Game(["rock", "paper", "scissors"]);
console.log(game.name);// Russian Roulette"
console.log(game.elements);// ["rock", "paper", "scissors"]

If it finds the constructor’s property, it takes it, no matter what you set in the prototype. However, if the property only exists in the prototype, it takes it.

Conclusion

Prototypes are an efficient way to share properties across multiple instances.

They optimize memory consumption and CPU usage. They also make JavaScript exquisitely modular as long as you use it properly.