Excited! Let's start then...
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.
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
- Define with a capitalized name to distinguish them from other functions
- Constructors use the keyword
this
to set the properties of the object they will create. - 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
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
- own properties
- 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.
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
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 ofAnimal
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
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
We are at the end! Hope you enjoyed reading this article and learnt something new today
Resources Used:
- Free code camp
- MDN Docs
PS: I'll keep on adding and improving the content of this article as I learn more about these concepts!