You might have thought Javascript to only be a functional programming langauge, but it's technically both Object Oriented and Functional!!

Today, I will be showing you how to write objects in 4 different instantiation patterns.

  1. Manual creation of an object
  2. Functional instantiation
  3. Functional instantiation with shared methods
  4. Prototypal instantiation
  5. Pseudoclassical instantiation

0. Manual Object Creation with methods

The reason for all of this, is that creating objects manually without a class is tedious. But before we begin, I think it's super important that you realize why these instantiation patterns are important.

Let say you want to create a person named Arian, whose age is 25. You also wanted to create another person object that was named Michael, whose age is 23.

What if you also needed to calcuate what year a person was born in? or if you needed to compare two peoples age?

You might do the following:

var arian = {  
  name: 'Arian',
  age: 25,
  calcBorn: function() {
    var currentYear = new Date().getFullYear();
    return currentYear - this.age;
  }
};

var michael = {  
  name: 'Michael',
  age: 23,
  calcBorn: function() {
    var currentYear = new Date().getFullYear();
    return currentYear - this.age;
  }
};

arian.calcBorn();    // 1990  
michael.calcBorn();  // 1992

var diff = arian.age - michael.age; // 2  

But notice how you had to rewrite the calcBorn function twice!

I kind of cheated by doing var diff = arian.age - michael.age;. The appropriate way to do this is to add a calcDiff function (or method) on the object.

var arian = {  
  name: 'Arian',
  age: 25,
  calcBorn: function() {
    var currentYear = new Date().getFullYear();
    return currentYear - this.age;
  },
  calcDiff: function(otherPerson){
    return otherPerson.age - this.age;
  }
};

var michael = {  
  name: 'Michael',
  age: 23,
  calcBorn: function() {
    var currentYear = new Date().getFullYear();
    return currentYear - this.age;
  },
  calcDiff: function(otherPerson){
    return otherPerson.age - this.age;
  }
};

arian.calcBorn();    // 1990  
michael.calcBorn();  // 1992

var diff = michael.calcDiff(arian); // 2  

Notice how each time we create a new method, we have to repeat ourselves. Remember to always practice DRY (Don't Repeat Yourself) coding. Now imagine if you had to create an object for 5 people, what about 100 people? This is why we have the next 4 instatiation patterns!

1. Functional Instantiation

The first, and most simple way to do this, is Functional Instantiation. We basically make a function that returns the object we need. Makes sense right!

/* Functional Class */
var person = function(name, age) {

  var newPerson = {};

  newPerson.name = name;
  newPerson.age = age;

  newPerson.print = function() {
    console.log(newPerson.name + " is " + newPerson.age + " years old.");
  };

  newPerson.calcBorn = function() {
    var currentYear = new Date().getFullYear();
    return currentYear - newPerson.age;
  };

  newPerson.calcDiff = function(otherPerson){
    return otherPerson.age - newPerson.age;
  };

  return newPerson;
};

var arian = person('Arian', 25);  
var michael = person('Michael', 23);

arian.print();      // "Arian is 25 years old."  
arian.calcBorn();   // 1990  
michael.print();    // "Michael is 23 years old."  
michael.calcBorn(); // 1992

var diff = michael.calcDiff(arian); // 2  

Look at how much nicer it is when you can just make a function call to create an object.

2. Functional instantiation with shared methods

This pattern is the same as the above one, however with one twist! So before when we were creating a person, each person had it's own method. These methods were actually attached to the object every time a person was created. Now if we had 1000 objects, it would take up quite a bit of memory to have each object have its own methods.

That's why there is an improved version with shared methods since all people will have the same exact methods.

/* Functional-Shared Class */
var person = function(name, age) {

  var newPerson = {};

  newPerson.name = name;
  newPerson.age = age;

  extend(newPerson, person.method);

  return newPerson;
};

var extend = function(to, from) {  
  for (var key in from) {
    to[key] = from[key];
  }
};

person.method = {};

person.method.print = function() {  
  console.log(this.name + " is " + this.age + " years old.");
};

person.method.calcBorn = function() {  
  var currentYear = new Date().getFullYear();
  return currentYear - this.age;
};

person.method.calcDiff = function(otherPerson){  
  return otherPerson.age - this.age;
};

var arian = person('Arian', 25);  
var michael = person('Michael', 23);

arian.print();      // "Arian is 25 years old."  
arian.calcBorn();   // 1990  
michael.print();    // "Michael is 23 years old."  
michael.calcBorn(); // 1992

var diff = michael.calcDiff(arian); // 2  

There are some important details to note, we had to create an extend function that extends the person method to the person class. So only the reference gets copied each time an object is created.

Notice how we had to use the this variable in the methods to reference variables in the object returned by the main function.

The interesting thing to note, is that we set person equal to a function, then we used it as an object (person.method)! This is allowed in Javascript, it turns the person variable into an object, with a function set to the root of the object, not at any key value. It looks like this when you console.log(person) with node:

{ [Function]
  method: { print: [Function], calcBorn: [Function], calcDiff: [Function] } }

We could store the person methods somewhere else than person.methods, but it fits nicely under the class variable.

3. Prototypal Instantiation

Creating the extends function was neat, but it would be a pain to create it everytime you wanted to use a class. Yep, there is a better way to add the methods. You can add the method object to the prototype chain. The prototype chain is where Javascript looks to see if a method exists. It will look at the current object, if it fails to find the method, it will then look at its prototype, and if it fails again it will repeat the process.

To add an object to an objects prototype chain, you use Object.create(XXX). This method will return an object with XXX as its prototype.

This is how you can incorporate it in a class

/* Prototypal Class */
var person = function(name, age) {  
  var newPerson = Object.create(person.prototype);

  newPerson.name = name;
  newPerson.age = age;

  return newPerson;
};

person.prototype.print = function() {  
  console.log(this.name + " is " + this.age + " years old.");
};

person.prototype.calcBorn = function() {  
  var currentYear = new Date().getFullYear();
  return currentYear - this.age;
};

person.prototype.calcDiff = function(otherPerson){  
  return otherPerson.age - this.age;
};

var arian = person('Arian', 25);  
var michael = person('Michael', 23);

arian.print();      // "Arian is 25 years old."  
arian.calcBorn();   // 1990  
michael.print();    // "Michael is 23 years old."  
michael.calcBorn(); // 1992

var diff = michael.calcDiff(arian); // 2  

4. Pseudoclassical Instantiation

That pseudoclassical pattern makes it even easier to create a class. To use this pattern it's important to note that you will need to use the new keyword when creating a new person of a class.

For example:

var arian = new Person();  

when using the new keyword, javascript automatically adds the commented section below:

var Person = function(name, age) {  
  /* this = Object.create(Person.prototype); */

  /* return this; */
};

Notice how this is very similar to how Prototypal Instantiation works, but it does some of the work for you when you use the new keyword.

But you might not know whether the class needs to be created with the new keyword, unless you spent time looking at the class. To solve this problem, it's convention to capitlize the variable for classes that require the keyword new.

That's why person is now Person.

So here is how you implement this for the current example:

/* Pseudoclassical */

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

Person.prototype.print = function() {  
  console.log(this.name + " is " + this.age + " years old.");
};

Person.prototype.calcBorn = function() {  
  var currentYear = new Date().getFullYear();
  return currentYear - this.age;
};

Person.prototype.calcDiff = function(otherPerson){  
  return otherPerson.age - this.age;
};

var arian = new Person('Arian', 25);  
var michael = new Person('Michael', 23);

arian.print();      // "Arian is 25 years old."  
arian.calcBorn();   // 1990  
michael.print();    // "Michael is 23 years old."  
michael.calcBorn(); // 1992

var diff = michael.calcDiff(arian); // 2