All IN ONE! OOPS, IN JS

Object-Oriented Programming concepts...in JS

All IN ONE! OOPS, IN JS

Excited! Let's start then... exicted

What is Object?

An object is a collection of related data and functionality.

Object in JS is used to model real-world objects for eg: car, birds etc with properties and behaviour.

For example,

const person = {
    name: "Bob Smith",
    interest: ["Music","Dancing"]
}

So, name and interest are the properties of Object person. This method of creating an object is called object literal!

Access Properties (Dot Notation)

To access properties, we will use dot notation i.e objectName.propertyName For example, in the above example, We have objectName as person and propertyName is the name and interest. So to access those we have,

console.log(person.name)
console.log(person.interest)

Note: We can also access properties by bracket notation

Method: Special Property of Object

Yes, so the method is a special property of an object which is defined as a function. It adds behaviour to the object! It allows doing something with data.

For example

const person = {
    name: "Bob Smith",
    interest: ["Music","Dancing"],
    greet: function() { 
    return "Welcome " + person.name
   }      
}
person.greet() // "Welcome Bob Smith"

If we observe, we are accessing name property inside method greet by person.name but to make code reusable we'll use this keyword.

In the above example, we saw how can we access the name of object person inside method greet. But imagine, if I have a very big object with 10,000 properties and I am accessing name property 4000 times. Then changing them all is a cumbersome process and also can introduce errors in the code.

So to avoid errors we access properties inside the method with the help of the this keyword!
Modifying the above example as follow:

const person = {
    name: "Bob Smith",
    interest: ["Music","Dancing"],
    greet: function() { 
    return "Welcome " + this.name
   }      
}

person.greet()

Note: Here, the this keyword refers to the object person through which the greet method is associated with.

Therefore this keyword helps to reuse code more easily.

wow

Constructor Function

Constructors are functions that create new objects. They define properties and behaviors that will belong to the new object.

For Example

function Person(name) {
  this.name = name;
  this.greeting = function() {
    alert('Hi! I\'m ' + this.name + '.');
  };
}

Few Conventions of Constructor function are

  1. Define with a capitalized name to distinguish them from other functions
  2. Constructors use the keyword this to set the properties of the object they will create.
  3. Constructors define properties and behaviors instead of returning a value!

Note: The constructor function is JavaScript's version of a class. Note: Constructor function doesn't return anything explicit.

Use Constructor to create new Objects

let person1 = new Person('Bob');
let person2 = new Person('Sarah');

the new operator is used when calling a constructor. This tells JavaScript to create a new instance of Person called person1. Without the new operator, this inside the constructor would not point to the newly created object, giving unexpected results. Now, person1 has all the properties defined inside the Person constructor.

Now properties can be accessed as follows:

person1.name
person1.greeting()
person2.name
person2.greeting()

and can be modified:

person1.name = 'John Doe';

Note: Remember, when we are calling our constructor function, we are defining greeting() every time, which isn't ideal. To avoid this, we can define functions on the prototype instead!

Note: Uptil now, we have seen 2 ways to create Objects i.e Object literal and constructor function. But there are other ways also

For example,

// some more ways to create Objects
// 1st way - Object Literal
// 2nd way - Constructor Function

// Using Object Constructor Function
let person1 = new Object()  //creates a empty Object, you can then add properties and methods into this with help of dot notation!

//Passing Object literal

let person1 = new Object({
name: 'Chris',
  age: 38,
  greeting: function() {
    alert('Hi! I\'m ' + this.name + '.');
  }
})

// Using create(), we can create Objects without first creating the constructor Function
//With it, you can create a new object, using an existing object as the prototype of the newly created object.

let person2 = Object.create(person1);
//Remember `person1` is an Object already created which is passed inside!
//One limitation of create() is that IE8 does not support it. So constructors may be more effective if you want to support older browsers.

Extend Constructor to receive arguments

Whenever we create a new object from Person, it would assign the same properties value to all objects, which I don't want. I want every person should have their own name and age properties, so for that, we need to pass arguments into constructor function and make objects accordingly!

Example

function Person(name,age) {
this.name = name,
this.age = age,
this.brain = 1
}

const person3 = new Person('Rishi', 6)

Verify an Object's Constructor with instanceof

Anytime a constructor function creates a new object, that object is said to be an instance of its constructor. We can verify this with the instanceof operator. It returns true or false based on whether or not that object was created with the constructor.

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

const person4 = new Person("Payal")
 person4 instanceof  Person  // returns true

Own Properties

In the example

function Person(name) {
  this.name  = name;
  this.eyes = 2;
   this.greeting = function() {
    alert('Hi! I\'m ' + this.name + '.');
  };
}

let person5 = new Person("Ankita");
let person6 = new Person("Supriya");

constructor Person has name, eyes, greeting property which is called own properties because they are defined directly on the instance of an object.

We can check this with hasOwnProperty() method

let ownProps = [];

for (let property in person5) {
  if(person5.hasOwnProperty(property)) {
    ownProps.push(property);
  }
}

console.log(ownProps);   // returns name, eyes and greeting

wow

Prototype

Prototypes are the mechanism by which JavaScript objects inherit features from one another

We have eyes property which gets copied in all Person instances.

To avoid these duplicate variables in each instance, we can add eyes in the prototype of Person. It will be available to all of its instances.

Person.prototype.eyes = 2;

Note: So we have seen 2 types of properties

  1. own properties
  2. prototype properties

Adding multiple Properties in prototype at Once

Person.prototype = {
  eyes: 2, 
  greeting: function() {
    console.log("Welcome");
  }
};

Drawback of Adding Prototype properties manually

So, the drawback is, it erases the constructor property.

To fix this, whenever a prototype is manually set to a new object, remember to define the constructor property:

Person.prototype = {
  constructor: Person,
  eyes: 2, 
  greeting: function() {
    console.log("Welcome");
  }
};

Understand Where an Object’s Prototype Comes From

Just like people inherit genes from their parents, an object inherits its prototype directly from the constructor function that created it. For example, here the Person constructor creates the person object:

function Person(name) {
  this.name = name;
}
let person = new Person("Kanchan");

The person Object inherits its prototype from the Person constructor function. You can show this relationship with the isPrototypeOf method:

Person.prototype.isPrototypeOf(person);  // returns true

Understand the Prototype Chain

All objects in JavaScript (with a few exceptions) have a prototype. Also, an object’s prototype itself is an object.

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

typeof Person.prototype;  // returns Object

Because a prototype is an object, a prototype can have its own prototype! In this case, the prototype of Person.prototype is Object.prototype:

Object.prototype.isPrototypeOf(Person.prototype);  // returns true

How is this useful? You may recall the hasOwnProperty method:

let person = new Person("Payal");
person.hasOwnProperty("name");

The hasOwnProperty method is defined in Object.prototype, which can be accessed by Person.prototype, which can then be accessed by person.

This is an example of the prototype chain. In this prototype chain, Person is the supertype for the person object, while person is the subtype. The Object is a supertype for both Person and person. The Object is a supertype for all objects in JavaScript. Therefore, any object can use the hasOwnProperty method.

prototype

Note: The methods and properties are not copied from one object to another in the prototype chain. They are accessed by walking up the chain

god

Inheritance

Creating child objects classes from parent classes

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

Cat.prototype = {
  constructor: Cat,
  eat: function() {
    console.log("nom nom nom");
  }
};

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

Bear.prototype = {
  constructor: Bear,
  eat: function() {
    console.log("nom nom nom");
  }
};

function Animal() { }

Animal.prototype = {
  constructor: Animal,
};

As you can see that eat() method is repeated in both Bear and Cat.
Note: Don't Repeat Yourself (DRY), therefore, we need an inheritance!
So to apply DRY, we will

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

Cat.prototype = {
  constructor: Cat,
};

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

Bear.prototype = {
  constructor: Bear,
};

function Animal() { }

Animal.prototype = {
  constructor: Animal,
   eat: function() {
    console.log("nom nom nom");
  }

};

So Animal() is supertype of Cat() and Bear()

How to use Behaviour of supertype?

So to use the behaviour of supertype Animal(), we will use a technique called Inheritance.

There are 2 steps to do this:

  • Make an instance of supertype i.e Animal()
let animal = new Animal();  //this instance will be complex for inheritance
  `OR`
let animal = Object.create(Animal.prototype);  // we will use this approach
  • set the prototype of the subtype (or child)—in this case, Cat—to be an instance of Animal
Cat.prototype = Object.create(Animal.prototype);

Remember that the prototype is like the "recipe" for creating an object. In a way, the recipe for Cat now includes all the key "ingredients" from Animal.

Yes, so this way we inherited the properties of Animal. But now if we check the constructor of an instance of Cat, it will show Animal! So we need to reset it back to Cat!

Reset Inherited constructor property

Cat.prototype.constructor = Cat;

Note: Setting constructor to Cat is important instead of Animal, because later it can create some problems.

Add Methods After Inheritance

A constructor function that inherits its prototype object from a supertype constructor function can still have its own methods in addition to inherited methods.

Cat.prototype.fly = function() {
  console.log(" Oops, I cannot fly !");
};

Note: Now instances of Cat will have both eat() and fly() methods:

Override inheritance methods

ChildObject.prototype = Object.create(ParentObject.prototype);
// We inherited like this...

It's possible to override an inherited method. It's done the same way - by adding a method to ChildObject.prototype using the same method name as the one to override. Here's an example of Cat overriding the eat() method inherited from Animal

function Animal() { }
Animal.prototype.eat = function() {
  return "nom nom nom";
};

function Cat() { }

Cat.prototype = Object.create(Animal.prototype);

Cat.prototype.eat = function() {
  return "Meowww... I am eating";
}

If you have an instance let meow = new Cat(); and you call meow.eat(), this is how JavaScript looks for the method on the prototype chain of meow:

  • meow => Is eat() defined here? No.
  • Cat => Is eat() defined here? => Yes. Execute it and stop searching.
  • Animal => eat() is also defined, but JavaScript stopped searching before reaching this level.
  • Object => JavaScript stopped searching before reaching this level.

With ES5, we have the class keyword for inheritance

class Person {
  constructor(first, last, age, gender, interests) {
    this.name = {
      first,
      last
    };
    this.age = age;
    this.gender = gender;
    this.interests = interests;
  }

  greeting() {
    console.log(`Hi! I'm ${this.name.first}`);
  };

  farewell() {
    console.log(`${this.name.first} has left the building. Bye for now!`);
  };
}

Creating an instance of the class

let han = new Person('Han', 'Solo', 25, 'male', ['Football']);
han.greeting();
// Hi! I'm Han

Note: Under the hood, your classes are being converted into Prototypal Inheritance models — this is just syntactic sugar.

Inheritance with class syntax

class Teacher extends Person {
  constructor(first, last, age, gender, interests, subject, grade) {
    super(first, last, age, gender, interests); // Now 'this' is initialized by calling the parent constructor.
    this.subject = subject;
    this.grade = grade;
  }
}

Note: For sub-classes, this initialization to a newly allocated object is always dependant on the parent class constructor, i.e the constructor function of the class from which you're extending!

To call the parent constructor we have to use the super() operator lay-down

Use a Mixin to Add Common Behavior Between Unrelated Objects

As you have seen, behavior is shared through inheritance. However, there are cases when inheritance is not the best solution. Inheritance does not work well for unrelated objects like Cat and Person. They can both eat, but a Cat is not a type of Person and vice versa.

For unrelated objects, it's better to use mixins. A mixin allows other objects to use a collection of functions.

let flyMixin = function(obj) {
  obj.fly = function() {
    console.log("Flying, wooosh!");
  }
};

The flyMixin takes any object and gives it the fly method.

let cat = {
  name: "Donald",
};

let person = {
   name: "Tappu"
};

flyMixin(cat);
flyMixin(person)

Note: You can use closure's to make properties of the class private!

Congratulations

congrats

We are at the end! Hope you enjoyed reading this article and learnt something new today

Resources Used:

  1. Free code camp
  2. MDN Docs

PS: I'll keep on adding and improving the content of this article as I learn more about these concepts!