The 3 Types of Scope :

Pasted image 20231215161530.png

Scope Chain :

Pasted image 20231215161930.png

Hoisting :

Pasted image 20231215165124.png

  • function expression and arrows basically behave as the variable type that is used to define it.

Temporal Dead Zone, Let & Const :

Pasted image 20231215170611.png

The this keyword :

Pasted image 20231216103043.png

// The this keyword in the global scope is simply the 'window' object
console.log(this); // Prints: Window

// 'this' keyword when we are using a simple function call
const calculateAge = function (currYear, birthYear) {
    console.log(this); // Prints: undefined. Because calculateAge method is being called as a simple function call.
    return birthYear - currYear;
}
calculateAge(2020, 1990);

// 'this' keyword when we are using a function call for an arrow function
// Recall arrow functions do not get their own 'this' keyword.
// Instead it refers to the 'this' of the parent function/scope
// In this case the parent function/scope is just the global scope, and hence we get window
const calculateAgeArrow = (currYear, birthYear) => {
    console.log(this); // Prints: Window. Because 'this' in arrow function refers to the parent function, which is the global scope in this case.
    return birthYear - currYear;
}
calculateAgeArrow(2020, 1990);

// 'this' keyword when we are using a method i.e. a function attached to a object
// When we use the 'this' keyword inside of a method, then the 'this' keyword refers to the object that is calling the method
// In this case, it is the dave object
const dave = {
    birthYear: 1991,
    calcAgeDave: function (currYear) {
        console.log(this); // Prints: 'dave'. Because 'this' refers to the dave object
        console.log(currYear - this.birthYear); // Hence we can use 'this' to reference the properties in the object
    }
}
dave.calcAgeDave(2021);

// Note that the 'this' keyword points to the object calling the method,
// not the object in which we wrote the method
// Let us see an example by doing this:
const eve = {
    birthYear: 2000
};
// Since functions are just variables, we can write this
// This will add a new property to the eve object that will contain the calcAgeDave function from the dave object
// This is often called: Method Borrowing
eve.calcAgeEve = dave.calcAgeDave;
// So now let's call the method calcAge. What do you think the 'this' will refer to?
// It refers to the eve object now.
// This shows that the 'this' keyword points to the object that is calling the method, and not the object that contains the method
// This is called borrowing.
eve.calcAgeEve(2021); // Output : 21 (as 2021 -2000 = 21)

// We can go even one step further, and just take the function completely out of the object
// Now the 'this' keyword prints 'undefined' because we are making a simple function call
// and we know that in those, the 'this' keyword is just 'undefined'
// BASICALLY this.birthYear nahi raha ab (sed lyf 😔)
const myFunc = dave.calcAgeDave;
myFunc(2021); // Prints: undefined

Regular Functions vs Arrow Functions :

  • One important point is that arrow functions should never be used as methods on an object. You should always use function expressions. This is to do with the fact that arrow functions do not get their own this keyword, instead they inherit the this keyword from their parent scope. This can be a source of bugs.
'use strict';

const jonas = {
  firstName: 'Jonas',
  year: 1991,
  calcAge: function () {
    console.log(this);
    console.log(2037 - this.year);
  },
  greet: () => {
    console.log(this);
    console.log(`Hey ${this.firstName}`);
  },
};

//here note that the jonas object is not a code block and doesn't have it's own scope so when we use 'this' in the greet arrow function we'll reach for the global scope and not the jonas object as this keyword when used in arrow functions always points to the parent scope.
jonas.greet();
// Output :
// window
// Hey undefined

console.log(this.firstName); // Output : undefined (as window object doesn't have firstName)

//however had we created a `var firstName = 'Matilda'` in the global scope we would've gotten the result of this.frstName above as 'Matilda' because 'this' there was pointing to the window object and var firstName is also create in the window object, this is why using var sometimes becomes very dangerous.

// Best Practice : Never ever use an arrow function as a method.

/* ------------------------------------------------------ */

// Consider the following function inside a method. Why does line 42 print undefined? If you look closely, you can see that the isMillennial function is being called as a simple function call. So, even though the function is within a method, it is still a regular function call. And the rule says that inside a normal function call, the this keyword is undefined.

const jonas2 = {
  firstName: 'Jonas',
  year: 1991,
  calcAge: function () {
    // console.log(this);
    console.log(2037 - this.year);
    /* ------------------------ sol1 ------------------------ */
    // // using self or that

    // const self = this;
    // const isMillenial = function () {
    //     // console.log(this) // here this will return undefined it is a simple function call.
    //     console.log(self.year >= 1981 && self.year <=1996);

    // };
    // isMillenial();
    /* ------------------------------------------------------ */
    /* ------------------------ sol2 ------------------------ */
    // // using arrow function

    const self = this;
    const isMillenial = () => {
      console.log(self.year >= 1981 && self.year <= 1996);
    };
    isMillenial();
  },
};

jonas2.calcAge();

/* ------------------- arguments keyword -------------- */
console.log('---arguments---');

const addExpr = function (a, b) {
  console.log(arguments);
  return a + b;
};
addExpr(2, 5); // logs : { '0': 2, '1': 5 }
addExpr(2, 5, 8, 12); //logs : { '0': 2, '1': 5, '2': 8, '3': 12 }

//however the arrow function does not work with the arguments keyword

var addArrow = (a, b) => {
  console.log(arguments);
  return a + b;
};
// addArrow(2, 5, 8); //output : Uncaught ReferenceError: arguments is not defined

Primitives vs Objects :

Pasted image 20231216144426.png

let age = 30;
let oldAge = age;
age =31;
console.log(age);
console.log(oldAge);

const me = {
    name: 'Jonas',
    age: 30,
};

const friend = me;
friend.age = 27;
console.log('Friend: ', friend);
console.log('Me: ', me);

Pasted image 20231216145156.png

let age = 30;
let oldAge = age;
age =31;
console.log(age);
console.log(oldAge);

const me = {
    name: 'Jonas',
    age: 30,
};

const friend = me;
friend.age = 27;
console.log('Friend: ', friend);
console.log('Me: ', me);

/* ------------- to actually copy the object ------------ */
const jessica2= {
    firstName: 'Jessica',
    lastName: 'Williams',
    age: 27,
};

// Object.assign() is used to merge two objects and then return the merged object.
//here we'll merge the jessica2 object with an empty object:
const jessicaCopy = Object.assign({}, jessica2);
jessicaCopy.lastName = 'Davis';

// both jessica2 and jessicaCopy are different objects now
console.log('Before marriage: ', jessica2);
console.log('After marriage: ', jessicaCopy);
  • Notice that Object.assign() only creates a shallow copy. If we have an object inside another object then the inner object will get passed by address and it'll still be the same.
  • for e.g. :
/* ------------- to actually copy the object ------------ */
const jessica2= {
    firstName: 'Jessica',
    lastName: 'Williams',
    age: 27,
    family: ['Alice', 'Bob'],
};

// Object.assign() is used to merge two objects and then return the merged object.
//here we'll merge the jessica2 object with an empty object:
const jessicaCopy = Object.assign({}, jessica2);
jessicaCopy.lastName = 'Davis';

// both jessica2 and jessicaCopy are different objects now
// console.log('Before marriage: ', jessica2);
// console.log('After marriage: ', jessicaCopy);

jessicaCopy.family.push('Mary');
jessicaCopy.family.push('John');

// both 'family' objects now have 'Mary' and 'John'. 
console.log('Before marriage: ', jessica2);
console.log('After marriage: ', jessicaCopy);

// we'll learn deep cloning later in the course which'll hellp use to clone an object inside another object using JS libraries such as Lo-Dash