Scope, Hoisting and Closures in Javascript
Published: 2023-03-18 04:05:33

### Scope

Scope in JavaScript refers to the accessibility of variables, functions, and objects in a particular part of the code. JavaScript uses lexical scoping, which means that the scope of a variable or function is determined by its location in the code.
Before getting into scope we have to know how we can declare variables in javascript. There are three methods in javascript to declare variables ie. let var and const.
* The variables declared with a var will have function scope.
Example
```js
function foo() {
  var a = 1;
  console.log(a);
}

foo(); // Output: 1
console.log(a); // Error: a is not defined

```
* If a variable is declared in a function using var then it can be accessible in the function only but if it is defined in the block it will have block scope it can be accessed anywhere in the program
Example
```js
if(2 < 3){
  var a = 1;
  console.log(a);
}

console.log(a); // Output: 1

```
* In javascript nested functions can access variables in their parent function these nested functions are called closures.
  Example 
```js
  function outer() {
  var a = 1;

  function inner() {
    var b = 2;
    console.log(a, b);
  }

  inner();
}

outer(); // Output: 1 2
```

* The variables declared using let and const will have block scope means any variable declared within curly braces cannot accessible from outside of curly braces.
Example
```js
if(2 < 3){
  var a = 1;
  console.log(a);
}

console.log(a); // Output: undefined

```
* Nested functions can access variables declared in an outer function
* If a variable is declared outside all functions and blocks it will have global scope means it can be accessed from anywhere in the program.

### Hoisting
In javascript, execution will have two phases one is the creation and another one is execution. In the creation phase all variables will be created in memory with the default value undefined and all the functions will store fully. Here only variables declared with var and regular functions will be created in the creation phase. This is known as hoisting

* In the execution phase variable initialization will take place
Example
```js
console.log(hi) // undefined
console.log(hello) // reference error

var hi =90
let hello=99

```
```js
var hii=44
hi()

function hi(){
    console.log(hii);// output: 44
}
```
### Closures

In JavaScript, a closure is created when a function is defined inside another function and the inner function is returned or passed as a reference to a variable outside its enclosing function. The closure captures and retains the values of all the variables that were in scope at the time of its creation, even after the outer function has returned. Closures allow you to create private variables and functions that are inaccessible from outside the function in which they were defined

Example
```js
function outerFunction(x) {
  function innerFunction(y) {
    return x + y;
  }
  return innerFunction;
}

let addFive = outerFunction(5);
console.log(addFive(3)); // Output: 8

```
In this example, outerFunction returns innerFunction, which is then assigned to the variable addFive. When addFive is called with an argument of 3, it returns 8. The closure is created when innerFunction is defined inside outerFunction, and it captures the value of x, which is 5. Even though outerFunction has returned and its local variables are no longer in scope, the closure retains the value of x and can still access it when innerFunction is called.

Applications of closures
1. Private variables and methods: Closures can be used to create private variables and methods that are inaccessible from outside the function in which they were defined. This can be useful for preventing unintended modifications to your code. 
For example:
```js
function counter() {
  let count = 0;
  function increment() {
    count++;
    console.log(count);
  }
  return increment;
}

let c = counter();
c(); // Output: 1
c(); // Output: 2
c(); // Output: 3
```
2. Memoization: Closures can be used to implement memoization, which is a technique for caching the results of expensive function calls to improve performance. 
For example:
```js
function memoize(func) {
  let cache = {};
  return function(...args) {
    let key = JSON.stringify(args);
    if (cache[key]) {
      console.log('Retrieving from cache');
      return cache[key];
    }
    else {
      console.log('Calculating result');
      let result = func(...args);
      cache[key] = result;
      return result;
    }
  };
}

function fibonacci(n) {
  if (n < 2) return n;
  return fibonacci(n-1) + fibonacci(n-2);
}

let memoizedFibonacci = memoize(fibonacci);
console.log(memoizedFibonacci(10)); // Output: 55
console.log(memoizedFibonacci(10)); // Output: Retrieving from cache, 55

```
3. Callback functions: Closures can be used to create callback functions that have access to variables from the parent function. This can be useful for implementing event handlers and other asynchronous operations. 
For example:
```js
function doSomething(callback) {
  let data = 'hello world';
  setTimeout(function() {
    callback(data);
  }, 1000);
}

function callbackFunction(data) {
  console.log(data.toUpperCase());
}

doSomething(callbackFunction); // Output: HELLO WORLD
```