Sunday, 2 August 2020

Objects, Prototypes, and Classes in JavaScript

Object Literals :
==================
let person ={
  firstName:'Jim',
  lastName:'Cooper'
};

dynamic nature of javascript :
ex:

let person ={
  firstName:'Jim',
  lastName:'Cooper'
};
person.age=29;
person.isAdult=function(){ return this.age>=18;}

console.log(person.isAdult()); // true

ex 1:

let person ={
  firstName:'Jim',
  lastName:'Cooper',
  age:29,
  isAdult:function() {return this.age>=18;}
};


console.log(person.isAdult()); // true

ex 3:

function registerUser(fName,lName){
  let person ={
   firstName:fName,
   secondName:lName
  };
  console.log(person); // {firstName: "Jim", secondName: "Cooper"}
}

registerUser('Jim','Cooper');

ex : shorthand syntax


function registerUser(firstName,secondName){
  let person ={
   firstName,
   secondName
  };
  console.log(person); // {firstName: "Jim", secondName: "Cooper"}
}

registerUser('Jim','Cooper');

Object Literal Method Declaration :
=====================================

let person ={
   firstName:'Jim',
   secondName:'Cooper',
   age:18,
   isAdult() {return this.age>=18;}
};

person.isAdult(); // true

Inspecting Object Properties with Object.keys() and for...in :
==============================================================
Object.keys() accepts an object as argument and returns an array of all its(own) enumerable properties.

ex :

let person ={
   firstName:'Jim',
   secondName:'Cooper',
   age:18,
   isAdult() {return this.age>=18;}
};

Object.keys(person); // [firstName,secondName,age,isAdult]

for (let propertyName in person){
   console.log(propertyName);  // [firstName,secondName,age,isAdult]
}

so 'Object.keys' and 'for in' basically accomplish the same thing,
they give access to each of the property and method names on an object.

Object Equality and the Object.is() Function :
==============================================
Javascript Equality Operators

1.==
should be avoided. Useful only in rare cases.

Note : Not type-safe
ex :
"42" ==42 //true
0==false  // true
null==undefined // true
""==0      // true
[1,2] == "1,2"  // true


2.===
Most common.should be used in almost all cases.
Note : 
1.Type-Safe
2.Convenient/Concise
3.NaN not equal to NaN
 ex : NaN === NaN //false
4.+0 equals -0
  ex : -0 === +0 //true

3.Object.is()
Less common.Like === except for a few mathmatical differences.
   ex:  Object.is(person1,person2)
Note :
1.Type-Safe
2.Verbose   
3.NaN equals NaN
  ex : Object.is(NaN,NaN) // true
4.+0 does not equal -0  
  ex: Object.is(-0,+0) //false


Note : so for primitive types like strings, javascript compares their values,
but with objects,it compares their memory addresses.

Object Assign and Immutability :
=================================
Object.assign() method allows you to copy or merge the properties 
from one object to another object.

ex : 

let person1={
  firstName:'Jim',
  lastName:'Cooper',
  age: 29
};

let person2={};

Object.assign(person2,person1);

Object.is(person1,person2); //false
person1===person2           //false

Equality operator returns false because even though the properties
are the same between the two objects,they are not the same object in memory.

ex 2:
let person1={
  firstName:'Jim',
  lastName:'Cooper',
  age: 29
};

let healthStats = {
   height : 68,
   weight : 150
};

Object.assign(person1,healthStats);
console.log(person1); //{firstName: "Jim", lastName: "Cooper", age: 29, height: 68, weight: 150}

Note : Object.assign() merges the properties into that first object,
so it mutates it, and then it also returns the mutated object.

To avoid mutating the objects you're merging,it's helpful to pass in the empty object as the first parameter
to prevent any of the component objects from changing .

ex :

let person1={
  firstName:'Jim',
  lastName:'Cooper',
  age: 29
};

let healthStats = {
   height : 68,
   weight : 150
};

function mergeHealthStats(person,healthStats){
   return Object.assign({},person,healthStats);
}

let mergedPerson = mergeHealthStats(person1,healthStats);
console.log(mergedPerson); //{firstName: "Jim", lastName: "Cooper", age: 29, height: 68, weight: 150}

Object.assign method used with an empty object as the first parameter 
to avoid mutating the other objects.

Using Constructor Functions to Create Objects :
===============================================
'new' creates a new object,sets the context of the 'this' keyword to that new object.

ex: 
function Person(firstName,lastName){
 this.firstName = firstName;
 this.lastName = lastName;
}
let person=new Person('Jim','Cooper');
console.log(person); //{firstName: "Jim", lastName: "Cooper"}

using Object.create() :
=========================
let person2 = Object.create (
   Object.prototype,
   {
    firstName:{value:'Jim',enumerable:true,writable:true,configurable:true},
lastName:{value:'Cooper',enumerable:true,writable:true,configurable:true},
age:{value: 29,enumerable:true,writable:true,configurable:true}
   }
);

console.log(person2); // {firstName: "Jim", lastName: "Cooper", age: 29}

Object.create(proto,props) has two arguments.
The first argument i prototype for new object.
the second argument is the property descriptors.

Javascript Object Properties :
==============================

Using Bracket Notation to Access javascript properties.

ex : 

let person ={
  firstName:'Jim',
  lastName:'Cooper',
  age:29
};

console.log(person['lastName']); //Cooper

Note :
1.work with invalid Identifiers
2.Work with Variables

Square bracket notation allows access to properties
containing special characters and selection of properties using variables.

ex : const variable ='name';
     const obj = {name:'value'};
console.log(obj[variable]); // value

Modifying properties with property Descriptor :
===============================================
The Object.getOwnPropertyDescriptor() method returns a property descriptor
for an own property of a given object.

ex : 
let person ={
  firstName:'Jim',
  lastName:'Cooper',
  age:29
};

console.log(Object.getOwnPropertyDescriptor(person,"firstName"));
//{value: "Jim", writable: true, enumerable: true, configurable: true}

Object.defineProperty

syntax : Object.defineProperty(object,propertyName,descriptor);

By default a property defined with 'Object.defineProperty' isn't writable,
enumerable and configurable.


Note : if you make nested object read only,you're just preventing that pointer from being 
changed,so you can't point the name property to a new object.

ex : let person ={
       name:{
    firstName:'Jim',
secondName:'Cooper'
   },
  age:29 
 };
 
 Object.defineProperty(person,'name',{writable:false});
 person.name.firstName='Kris';
 console.log(person.name); // {firstName: "Kris", secondName: "Cooper"}
 
 'name' here is just a reference and javascript only prevents us from changing this 
 reference and will not stop us changing the properties of the object that this reference 
 points to.
 
 To make the object itself non-writable we can use Object's freeze method.
 
Note : Object.freeze works shallowly at the 'object' level,whereas
'Object.defineProperty' works at the property level.
Shallowly means,that if a property contains another object type,this object is  
affected by 'Object.freeze'. 

// Prevents any changes to an object
Object.freeze(object);

ex: 
  let person ={
       name:{
    firstName:'Jim',
secondName:'Cooper'
   },
  age:29 
 }; 
 
 Object.freeze(person.name);
 person.name.firstName='Kris';
 console.log(person.name); // {firstName: "Jim", secondName: "Cooper"}
 
 Using Enumerable Attribute :
 ============================
 By default, properties on an object are enumerable ,
 meaning we can enumerate over them with for in loops and list them with object.keys.
 
 ex:
 
 let person ={       
firstName:'Jim',
    secondName:'Cooper',    
age:29 
 }; 
 
 for(let propertyName in person){
   console.log(propertyName + ': '+ person[propertyName]);
 }

Output :
// firstName: Jim
// secondName: Cooper
// age: 29 

 ex :
  let person ={       
firstName:'Jim',
    secondName:'Cooper',    
age:29 
 }; 
 
 Object.defineProperty(person,'firstName',{enumerable:false});
 
 for(let propertyName in person){
   console.log(propertyName + ': '+ person[propertyName]);
 }
 
 output :
 // secondName: Cooper
 // age: 29
 
 Object.keys(person); // ["secondName", "age"]
 
 console.log(JSON.stringify(person)); // {"secondName":"Cooper","age":29}
 
 The 'enumerable' descriptor,when set to false on a property will hide it
 when looping over all properties using for...in loop.it also hides the property
 from the list of keys as obtained by Object.keys() and will not show up during 
 JSON serialization with JSON.stringify() as well.
 
Using Configurable Attribute :

Once 'configurable' property is set to false, it locks down that property and 
prevents from modifying 'enumerable' and even configurable.It also prevents from
deleting that property.

ex:  

let person ={       
firstName:'Jim',
    secondName:'Cooper',    
age:29 
 };

 Object.defineProperty(person,'firstName',{configurable:false });
 
 Object.defineProperty(person,'firstName',{enumerable:false }); // Cannot redefine property : firstName
 
 delete person.firstName; // cannot delete property 'firstName' 
 
 Object.defineProperty(person,'firstName',{configurable:true }); // Cannot redefine property : firstName

 So making a property non-configurable makes it so you can't change
 the configurable or enumerable attributes, and you can't delete the 
 property.
 
 Creating Property Getters and Setters :
 
 To create getters and setters,you have to use 'defineProperty'.
 
 ex: let person={
       name:{
     firstName:'Jim',
secondName:'Cooper'
   },
   age : 29
   };
   
   Object.defineProperty(person,'fullName',{get:function(){ return this.name.firstName + ' '+this.name.secondName;}});
   console.log(person.fullName); // Jim Cooper

ex 2 :
   
let person={
       name:{
     firstName:'Jim',
secondName:'Cooper'
   },
   age : 29
   };
   
   Object.defineProperty(person,'fullName',
   {get:function(){ 
      return this.name.firstName + ' '+this.name.secondName;
     },
    set:function(value){
  let nameParts=value.split(' ');
  this.name.firstName=nameParts[0];
  this.name.secondName=nameParts[1];
}
    });
person.fullName='Fred Jones';
   console.log(person.fullName); // Fred Jones

Javascript Prototypes and Interfaces :
======================================

Prototype:

A prototype is an object that exists on
every function in javascript.So every function in 
javascript has a prototype property.

ex : 
  let myFunction = function(){}
  console.log(myFunction.prototype); // {}
  
Objects do not have a prototype property.

ex : 
 let person={
   firstName:'Jim',
   lastName:'Cooper',
   age:29
 };  

console.log(person.prototype); // undefined 
   
ex : 
let person={
   firstName:'Jim',
   lastName:'Cooper',
   age:29
 }; 
 console.log(person.__proto__); // Object {}
 
 Object's prototype and a function prototype are used differently.
 
 Function's Prototype :
 A function's prototype is the object 'instance' that will become the prototype
 for all objects created using this function as a constructor.
 
 Object's Prototype :
 An Object's prototype is the object 'instance' from which the object is inherited.
 
 Note : A prototype,whether it's a functions prototype or an object's
 prototype, is actually an instance of an object in memory.
 
 
 ex :
 
 function Person(firstName,lastName){
  this.firstName=firstName;
  this.lastName=lastName;
 }
 
 console.log(Person.prototype); // Person {}
 let objJim=new Person('Jim','Cooper');
 console.log(objJim.__proto__); // Person {}
 console.log(Person.prototype === objJim.__proto__); // true
 
 ex :
 
 function Person(firstName,lastName){
   this.firstName=firstName;
   this.lastName=lastName;
 }
 
 Person.prototype.age=29;
 console.log(Person.prototype); // Person{ age: 19 }
 
 let jim = new Person('Jim','Cooper');
 let sofia = new Person('Sofia','Cooper');
 sofia.__proto__.age=19;

console.log(jim.__proto__); // Person { age: 19   } 
console.log(sofia.__proto__); // Person { age: 19 }

console.log(Person.prototype===jim.__proto__);

Note : The object instance that is the function's prototype 
becomes the prototype for all objects created from that prototype.

Instance vs Prototype Properties :

'hasOwnProperty' returns a boolean value indicating whether the object
on which you are calling it has a property with the name of the argument.

ex : 
function Person(firstName,lastName){
  this.firstName=firstName;
  this.lastName=lastName;
}

Person.prototype.age=29;
let jim=new Person('Jim','Cooper');
let sofia=new Person('Sofia','Cooper');

jim.age=18;

console.log(jim.hasOwnProperty('age')); // true
console.log(jim.age);  // 18

age value comes from Prototype

console.log(sofia.age); // 29



Note : hasOwnProperty does not look at the prototype chain of the object.

Changing a Function Prototype :

Change the function's prototype to point to a completely different object.

ex : function Person(firstName,secondName){
       this.firstName=firstName;
   this.secondName=secondName;
     } 
 
Person.prototype.age=29;
var jim=new Person('Jim','Cooper');
var sofia = new Person('Sofia','Cooper');

Person.prototype ={age:18};

console.log(Person.prototype); Object { age:18}
console.log(jim.age); // 29
console.log(sofia.age); // 29

let kris = new Person('Kris','Cooper');
console.log(kris.age);  // 18

Multiple level of Inheritance :

By default, all objects in javascript inherit from object, and object has no prototype.

ex : function Person(firstName,lastName){
       this.firstName=firstName;
   this.lastName=lastName;
     }
Person.prototype.age=24;
let jim=new Person('Jim','Cooper');
console.log(jim.__proto__);          // Person { age : 24 }
console.log(jim.__proto__.__proto__); // Object { }
console.log(jim.__proto__.__proto__.__proto__); // null
 
Creating Your Own Prototypal Inheritance Chains :

 ex :
   function Person(firstName,lastName,age){
     this.firstName=firstName;
this.lastName=lastName;
this.age=age;
Object.defineProperty(this,'fullName',{
   get:function(){
     return this.firstName+' '+this.lastName
   },
   enumerable:true
});
   } 

 function Student(firstName,lastName,age){
   Person.call(this,firstName,lastName,age);
   this._enrolledCourses=[];
   this.enroll=function(courseId){
     this._enrolledCourses.push(courseId);
   };
   
   this.getCourses = function(){
      return this.fullName+"'s enrolled Courses are : "+
         this._enrolledCourses.join(', '); 
   };
 
 }

Student.prototype=Object.create(Person.prototype);
Student.prototype.constructor = Student; 

let jim=new Student('Jim','Cooper',29);

console.log(jim); // Student {firstName: "Jim", lastName: "Cooper", age: 29,_enrolledCourses: Array(0), enroll: ƒ, getCourses: ƒ}
console.log(jim.__proto__); // Student {}
console.log(jim.__proto__.__proto__); // Person {}

jim.enroll('CS205');
jim.enroll('MA101');
jim.enroll('PS101');
console.log(jim.getCourses()); // Jim Cooper's enrolled Courses are : CS205, MA101, PS101

Javascript Classes :
======================
classes were introduced in ECMAScript version 6.
classes just offer a little cleaner syntax to accomplish it.

class Person{
 constructor(firstName,lastName,age){
  this.firstName=firstName;
  this.lastName=lastName;
  this.age=age;
  }
  // getter and setters
 get fullName(){
   return this.firstName+' '+this.lastName;
 }
 set fullName(fullName){
   var nameParts=fullName.split(' ');
   this.firstName=nameParts[0];
   this.lastName=nameParts[1];
 } 
 // adding functions
 isAdult() {
   return this.age >=18;
 }
}

let jim = new Person('Jim','Cooper',29);
console.log(jim); // {firstName: "Jim", lastName: "Cooper", age: 29}

console.log(jim.fullName); // Jim Cooper

jim.fullName='Fred Jones';
console.log(jim.fullName); // Fred Jones

console.log(jim.isAdult()); // true

Note : classes have prototypes,just like functions.
getter and setters live on the prototype,whereas other
properties live on the object instances.

console.log(jim); // {firstName: "Jim", lastName: "Cooper", age: 29}

Object.defineProperty(Person.prototype,'fullName',{enumerable:true}); 

console.log(jim); //{age: 29,firstName: "Jim",fullName: "Jim Cooper",lastName: "Cooper"}

class Student extends Person {
 constructor(firstName,lastName,age){
   super(firstName,lastName,age);
   this._enrolledCourses=[];
 }
 // static functions
 static fromPerson(person){
   return new Student(person.firstName,person.lastName,person.age);
 }
 
 enroll(courseId){
   this._enrolledCourses.push(courseId);
 }
 
 getCourses(){
   return this.fullName + "'s enrolled courses are : "+
          this._enrolledCourses.join(', ');
 }


let jim=new Student('Jim','Cooper',29);
jim.enroll('CS101');
console.log(jim.getCourses()); // jim Cooper's enrolled courses are : CS101


let jimStudent=Student.fromPerson(jim);

console.log(jimStudent); // Student { firstName:Jim lastName:Cooper age:29 _enrolledCourses: }

No comments:

Post a Comment