Class patterns

In object-oriented programming, a class is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods).

Wikipedia

There’s a special syntax construct and a keyword class in JavaScript. But before studying it, we should consider that the term “class” comes the theory of object-oriented programming. The definition is cited above, and it’s language-independant.

In JavaScript there are several well-known programming patterns to make classes even without using the class keyword. And here we’ll talk about them first.

The class construct will be described in the next chapter, but in JavaScript it’s a “syntax sugar” and an extension of one of the patterns that we’ll study here.

Functional class pattern

The constructor function below can be considered a class according to the definition:

function User(name) {
  this.sayHi = function() {
    alert(name;
  };
}

let user = new User("John");
user.sayHi(); // John

It follows all parts of the definition:

  1. It is a “program-code-template” for creating objects (callable with new).
  2. It provides initial values for the state (name from parameters).
  3. It provides methods (sayHi).

This is called functional class pattern.

In the functional class pattern, local variables and nested functions inside User, that are not assigned to this, are visible from inside, but not accessible by the outer code.

So we can easily add internal functions and variables, like calcAge() here:

function User(name, birthday) {

  // only visible from other methods inside User
  function calcAge() {
    new Date().getFullYear() - birthday.getFullYear();
  }

  this.sayHi = function() {
    alert(name + ', age:' + calcAge());
  };
}

let user = new User("John", new Date(2000,0,1));
user.sayHi(); // John

In this code variables name, birthday and the function calcAge() are internal, private to the object. They are only visible from inside of it. The external code that creates the user only can see a public method sayHi.

In works, because functional classes provide a shared lexical environment (of User) for private variables and methods.

Prototype-based classes

Functional class pattern is rarely used, because prototypes are generally better.

Soon you’ll see why.

Here’s the same class rewritten using prototypes:

function User(name, birthday) {
  this._name = name;
  this._birthday = birthday;
}

User.prototype._calcAge = function() {
  return new Date().getFullYear() - this._birthday.getFullYear();
};

User.prototype.sayHi = function() {
  alert(this._name + ', age:' + this._calcAge());
};

let user = new User("John", new Date(2000,0,1));
user.sayHi(); // John
  • The constructor User only initializes the current object state.
  • Methods reside in User.prototype.

Here methods are technically not inside function User, so they do not share a common lexical environment.

So, there is a widely known agreement that internal properties and methods are prepended with an underscore "_". Like _name or _calcAge(). Technically, that’s just an agreement, the outer code still can access them. But most developers recognize the meaning of "_" and try not to touch prefixed properties and methods in the external code.

We already can see benefits over the functional pattern:

  • In the functional pattern, each object has its own copy of methods like this.sayHi = function() {...}.
  • In the prototypal pattern, there’s a common User.prototype shared between all user objects.

So the prototypal pattern is more memory-efficient.

…But not only that. Prototypes allow us to setup the inheritance, precisely the same way as built-in JavaScript constructors do. Functional pattern allows to wrap a function into another function, and kind-of emulate inheritance this way, but that’s far less effective, so here we won’t go into details to save our time.

Prototype-based inheritance for classes

Let’s say we have two prototype-based classes.

Rabbit:

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype.jump = function() {
  alert(this.name + ' jumps!');
};

let rabbit = new Rabbit("My rabbit");

…And Animal:

function Animal(name) {
  this.name = name;
}

Animal.prototype.eat = function() {
  alert(this.name + ' eats.');
};

let animal = new Animal("My animal");

Right now they are fully independent.

But naturally Rabbit is a “subtype” of Animal. In other words, rabbits should be based on animals, have access to methods of Animal and extend them with its own methods.

What does it mean in the language on prototypes?

Right now rabbit objects have access to Rabbit.prototype. We should add Animal.prototype to it. So the chain would be rabbit -> Rabbit.prototype -> Animal.prototype.

Like this:

The code example:

// Same Animal as before
function Animal(name) {
  this.name = name;
}

// All animals can eat, right?
Animal.prototype.eat = function() {
  alert(this.name + ' eats.');
};

// Same Rabbit as before
function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype.jump = function() {
  alert(this.name + ' jumps!');
};

// setup the inheritance chain
Rabbit.prototype.__proto__ = Animal.prototype; // (*)

let rabbit = new Rabbit("White Rabbit");
rabbit.eat(); // rabbits can eat too
rabbit.jump();

The line (*) sets up the prototype chain. So that rabbit first searches methods in Rabbit.prototype, then Animal.prototype. And then, just for completeness, the search may continue in Object.prototype, because Animal.prototype is a regular plain object, so it inherits from it. But that’s not painted for brevity.

Here’s what the code does:

Summary

The term “class” comes from the object-oriented programming. In JavaScript it usually means the functional class pattern or the prototypal pattern. The prototypal pattern is more powerful and memory-efficient, so it’s recommended to stick to it.

According to the prototypal pattern:

  1. Methods are stored in Class.prototype.
  2. Prototypes inherit from each other.

In the next chapter we’ll study class keyword and construct. It allows to write prototypal classes shorter and provides some additional benefits.

Tasks

importance: 5

Find an error in the prototypal inheritance below.

What’s wrong? What are consequences going to be?

function Animal(name) {
  this.name = name;
}

Animal.prototype.walk = function() {
  alert(this.name + ' walks');
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = Animal.prototype;

Rabbit.prototype.walk = function() {
  alert(this.name + " bounces!");
};

Here’s the line with the error:

Rabbit.prototype = Animal.prototype;

Here Rabbit.prototype and Animal.prototype become the same object. So methods of both classes become mixed in that object.

As a result, Rabbit.prototype.walk overwrites Animal.prototype.walk, so all animals start to bounce:

function Animal(name) {
  this.name = name;
}

Animal.prototype.walk = function() {
  alert(this.name + ' walks');
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = Animal.prototype;

Rabbit.prototype.walk = function() {
  alert(this.name + " bounces!");
};

let animal = new Animal("pig");
animal.walk(); // pig bounces!

The correct variant would be:

Rabbit.prototype.__proto__ = Animal.prototype;
// or like this:
Rabbit.prototype = Object.create(Animal.prototype);

That makes prototypes separate, each of them stores methods of the corresponding class, but Rabbit.prototype inherits from Animal.prototype.

importance: 5

The Clock class is written in functional style. Rewrite it using prototypes.

P.S. The clock ticks in the console, open it to see.

Open a sandbox for the task.

Please note that properties that were internal in functional style (template, timer) and the internal method render are marked private with the underscore _.

Open the solution in a sandbox.

Tutorial map

Comments

read this before commenting…
  • You're welcome to post additions, questions to the articles and answers to them.
  • To insert a few words of code, use the <code> tag, for several lines – use <pre>, for more than 10 lines – use a sandbox (plnkr, JSBin, codepen…)
  • If you can't understand something in the article – please elaborate.