In javascript we can create an object using multiple ways, we will mention the most 3 used ways
-
Object literal :
var obj = { first : "Amr" , last : "Labib" }
-
using
new
keyword:var obj = new MyObjContructor("Amr" , "Labib")
-
using
Object.create
this is commonly used to set the created Object prototype and properties
I believe that the above 3 ways can be used to cover any use case for creating an object.
We use object literal, when we have a simple object with key-value pairs and we don't need to have multiple instances of that Object, in another word with singleton pattern.
var person = {
firstName : "Amr",
lastName : "Labib",
job : "Software Engineer",
printName : function() {
console.log(this.firstName + " " + this.lastName);
}
}
console.log(person.job); //Software Engineer
person.printName(); //Amr Labib
We use new
keyword when we start working with javascript in an OOP (Object Oriented Programming) way.
So to make use of Class multiple instances creation, constructors, inheritance and static functions we need to create our object using new
keyword
- A new object gets created (let's call it O).
- O gets linked to another object, called its
prototype
. - The function's
this
value is set to refer to O. - The function implicitly returns O.
//This is called constructor function, basically this is the class constructor
function Person(firstName , lastName , job){
this.firstName = firstName;
this.lastName = lastName;
this.job = job;
this.printName = function(){
console.log(this.firstName + " " + this.lastName);
}
}
//create 2 instances of Person class using new keyword with its constructor function
var person1 = new Person("Amr" , "Labib" , "Software Engineer");
var person2 = new Person("John" , "Adam" , "Doctor");
console.log(person1.job); //Software Engineer
person1.printName(); //Amr Labib
console.log(person2.job); //Doctor
person2.printName(); //John Adam
Note: When we define a constructor function as in the previous example, by convention we capitalize the first letter in the function name, so constructor functions should start with capitalized letter.
We use Object.create
when we need to create an object with specific prototype
var person = {
isHuman: false,
printDetails: function () {
console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
}
};
var me = Object.create(person);
me.name = "Amr"; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten
me.printDetails(); // My name is Amr. Am I human? true
person.printDetails(); // My name is undefined. Am I human? false
Every javascript object created using new
keyword will contain an object called prototype
.
We usually use object prototype
for:
-
Properties sharing between multiple instances of the same class
-
Inheritance between classes/objects.
In this example we will see how we can use object prototypes
to share printName
method.
function Person(firstName){
this.firstName = firstName;
}
//Here we define a new method called printName that will be shared by all instances created from Person class
Person.prototype.printName = function(){
console.log(this.firstName);
}
var person1 = new Person("Amr");
var person2 = new Person("John");
//Both instances share the same method printName
person1.printName(); //Amr
person2.printName(); //John
From Example 12.2 we can see that we can call printName
method on both instances person1
and person2
.
Javascript will check first if the current object instance has a property called printName
if it's not found it will start looking for that property inside the Object prototype, this is called prototype chain
Now we have a very important question, why can't we just define printName
method inside Person class as a class property like firstName
?
- We can do that, but using
prototype
will give us a better memory performance, as we are not creating the functionprintName
with eachPerson
instance instead we have a singleprintName
function that is shared by all created instancesperson1
andperson2
.
-
prototype
is set automatically when we create an object instance usingnew
keyword -
We set
prototype
only on the Object not on the object instance, as we can see in Example 12.2 we setprintName
function onPerson
not onperson1
orperson2
-
Using
prototype
will give a better memory performance specially when we need to create multiple instances of an Object as we create the function once instead of creating it with each instance.
-
prototype
is associated with ObjectsPerson.prototype.printName()
is valid whileperson1.prototype
isundefined
-
__proto__
is associated with objects instancesperson1.__proto__.printName()
is valid whilePerson.__proto__
isundefined
In few words __proto__
is created from prototype
and we should never change the value of __proto__
In the previous example we learned how we use object prototype
to make multiple instances share a specific method, and how this is good interms of performance.
Now we will see how to make Object inheritance, where we can have a subclass that inherit all properties and methods from a base class using prototype
//Base class Person
function Person (firstName , lastName){
this.firstName = firstName;
this.lastName = lastName;
}
//printName method is defined in base class prototype
Person.prototype.printName = function(){
console.log(this.firstName + " " + this.lastName);
}
Person.sayHi = function(firstName , lastName){
console.log("Hi " + firstName + " " + lastName);
}
//Subclass Engineer
function Engineer(firstName , lastName , job) {
//The line below is to adjust this context for inheritance
//Remember calling a function using call will make sure that the function `this` context is changed to whatever object is passed in the first argument.
//Here we call Person constructor with Engineer `this` context
Person.call(this, firstName , lastName);
this.job = job;
}
//This line will make sure that Engineer prototype is referencing its base class Person prototype
Engineer.prototype = Object.create(Person.prototype);
//We need this line because now constructor function of prototype object is set to Person constructor, so we need to change the reference back to Engineer constructor
Engineer.prototype.constructor = Engineer;
//We need to use Object.assign to copy the values of all enumerable own properties from Person to Engineer, this will make sure that static methods like sayHi is inherited by Engineer
Object.assign(Engineer , Person);
//printNameWithJob method is defined in subclass prototype only
Engineer.prototype.printNameWithJob = function() {
console.log(this.firstName + " " + this.lastName + ", " + this.job)
}
var person1 = new Person("Amr" , "Labib");
var engineer = new Engineer("Amr" , "Labib" , "Software Engineer");
person1.printName(); //Amr Labib
engineer.printName();//Amr Labib ---> this method is inherited from base class Person
engineer.printNameWithJob();//Amr Labib, Software Engineer ---> this method is defined in subclass only
Person.sayHi("Amr" , "Labib"); //Hi Amr Labib --> static method
Engineer.sayHi("Amr" , "Labib"); //Hi Amr Labib --> static method inherited from Person
person1.printNameWithJob(); //Uncaught TypeError: person1.printNameWithJob is not a function --> because printNameWithJob is defined in subclass only
We usually create static methods when we need to add a utility function to a class, that can be called with any arguments, example Math.abs(-2)
will return 2
var Calculator = function(){
}
Calculator.sum = function(num1 , num2){
return num1 + num2;
}
var calc1 = new Calculator();
console.log(Calculator.sum(1,2)); //3 ---> we can call sum directly on Calculator class
console.log(calc1.sum(1,2)); //Uncaught TypeError: calc1.sum is not a function ---> because sum is a static function.
As we can see in this example we can call sum
method without creating an instance of Calculator class, also if we tried to access sum
method from an instance it will return an error because it's a static method.