GUIDE: OOP In Javascript

❯❯ Constructor Functions

The classical way of object instantiation from a prototype in javascript (like Array, Map and Set are implemented), is using the constructor function.
We can use function declaration or function expressions for costructors, but not arrow functions (since we need 'this' keyword default binding).
The steps of instantiation using the 'new' keyword are:

1) Object is created (in heap memory).
2) 'this' is bound to the newly created object.
3) Object is link to prototype.
4) The object is returned after constructor termination.

Each constructor function creates a prototype property to which we can assign methods that all instances of that constructor inherit.

Use a constructor function and prototype property to create an object
click to copy code segment
const BankAccount = function (ownerName, balance) {
  this.ownerName = ownerName;
  this.balance = balance;
};
/* add two methods to the BankAccount constructor prototype */
BankAccount.prototype.deposit = function (amount) {
  if(amount > 0)
    this.balance += amount;
};
BankAccount.prototype.withdraw = function (amount) {
  if(amount > 0)
    this.balance -= amount;
};
BankAccount.prototype.getBalance = function () {
  return this.balance;
};
/* create a new bankAccount object */
const bankAccount = new BankAccount("John Smith", 35000);
/* use the prototype methods inherited by the BankAccount object */
bankAccount.withdraw(12000);
console.log(bankAccount.getBalance());

                              

br> The important thing to observe is that step (3) in using the 'new' keyword on a constructor function sets the instance __proto__ property to the prototype property of the constructor (which is not the prototype of the constructor itself!).

Some useful methods for checking prototypes and properties are:
constructorName.prototype.isPrototypeOf(instanceName)
instanceName.hasOwnProperty(propertyName)

❯❯ ES6 Classes

ES6 classes use the same prototypal inheritance mechanism with constructor functions, but just in a way that is more consistent with OOP syntactic schemes in Java or C#.

Using ES6 classes to instantiate objects
click to copy code segment
class BankAccount = {
  constructor(ownerName, balance) {
    this.ownerName = ownerName;
    this.balance = balance;
  }
  withdraw(amount) {
    if(amount > 0) 
      this.balance -= amount;
  }
  deposit(amount) {
    if(amount > 0) {
      this.balance += amount;
  }
  getBalance() {
    return this.balance;
  }
};

                              

Observe that using ES6 classes, we define object methods like in regular object literal definitions, but still behind the scenes, these method are attached and contained inside the object instance prototype. (as if we defined the method in BankAccount.prototype).

Accessors are special get/set methods that have a special syntax:

Create an object with get/set methods and use them
click to copy code segment
const bankAccount = {
  owner: "Johnny Sun",
  balance: 27609,

  get balance() {
    return this.balance;
  },
  set balance(newAmount) {
    this.balance = newAmount;
  },
};

/* show current balance */
console.log(bankAccount.balance);
/* change balance */
bankAccount.balance = 31934;

                              

Observe that with get/set methods we use the needed accessor keyword and when invoking it we write it as a property (without parentheses).
Getters and setters are mostly useful for data validation purposes.

To add static functions (meaning functions that are directly attached to a constructor function namespace, but are not in the prototype, so not inherited by instances of the class), we either use the static modifier keyword in a class definition or directly assign it to the constructor function.

❯❯ Using Object.create()

To create object instances using the Object.create() static method, we need to design a prototype from which instances will inherit their methods.

Using Object.create() to spawn object instances with some prototype
click to copy code segment
/* define a prototype object to then attach to instances */
const BankAccountPrototype = {
  deposit(amount) {
    if(amount > 0)
      this.balance += amount;
  },
  withdraw(amount) {
    if(amount > 0)
      this.balance -= amount;
  }
  /* init is used to initialize properties on object creation */
  init(balance) {
    this.balance = balance;
  }
};
/* create an instance by binding the prototype to it */
const bankAccount = Object.create(BankAccountPrototype);
const bankAccount2 = Object.create(BankAccountPrototype);
/* Create the balance property manually */
bankAccount.balance = 30000;
/* Once balance is defined we can use the methods inherited */
bankAccount.deposit(1000);
bankAccount.withdraw(650);
/* use init to initialize the second bank account */
bankAccount.init(15000);
/* Once balance is initialized we can use the methods inherited */
bankAccount.deposit(490);
bankAccount.withdraw(149);

                              

Observe that using Object.create(), an instance object can be linked to any prototype we wish (explicit linkage), while using the 'new' keyword implicitly links the instance to the prototype of the constructor function the 'new' keyword is applied on.

profile picture

WHO AM I?

Teacher
Thinker
Tinkerer