Hoisting of Variables, Functions, Classes, Types, Interfaces in JavaScript/TypeScript
Tags: javascript, typescript
Published: 2024-03-17 17:32:18

In the world of JavaScript, understanding the concept of hoisting can significantly improve the way you write and debug your code. Hoisting is one of those interesting behaviors of JavaScript that can sometimes lead to unexpected results, especially for those new to the language. In this article, we'll explore what hoisting is, how it works, and look at some examples to get a clearer picture.

##TOC
  - [Variable Hoisting](#Variable_Hoisting)
  - [Function Hoisting](#Function_Hoisting)
  - [Class Hoisting](#Class_Hoisting)
  - [Type Alias & Interface "Hoisting" in TypeScript](#Type_Alias)
  - [Tricky Tasks](#Tricky_Tasks)
  - [Solutions](#Solutions)

<a name="Variable_Hoisting"></a>
##Variable Hoisting
Variable hoisting is a JavaScript mechanism where variable declarations are moved to the top of their containing scope before the code has been executed. This means that no matter where variables are declared, they are moved to the top of their scope by the JavaScript interpreter.

Importantly, only the declarations are hoisted, not the initializations. This distinction is crucial for understanding some of the puzzling behavior that can arise.

To illustrate how variable hoisting works, let's look at a few examples.


**Example 1: Basic Variable Hoisting**
```javascript
console.log(myVar); // Output: undefined
var myVar = 5;
```

In this example, you might expect a ReferenceError since `myVar` is printed before it's declared. However, due to hoisting, the JavaScript interpreter sees the code as follows:

```javascript
var myVar;
console.log(myVar); // Output: undefined
myVar = 5;
```


**Example 2: `let` and `const` Hoisting**

The introduction of `let` and `const` in ES6 has somewhat altered the landscape of hoisting. Unlike `var`, which is hoisted to the top of its scope, `let` and `const` are hoisted to the top of their block scope but are not initialized, leading to what is called a "Temporal Dead Zone" (TDZ).

```javascript
console.log(myLetVar); // ReferenceError: Cannot access 'myLetVar' before initialization
let myLetVar = 3;
```

In this scenario, `myLetVar` is hoisted, but trying to access it before declaration results in a ReferenceError.


**Example 3: Re-declaring Variables with `var`**

```javascript
var myVar = 1;
console.log(myVar); // Output: 1

var myVar = 2;
console.log(myVar); // Output: 2
```

In this example, despite `myVar ` being declared twice, JavaScript's hoisting mechanism processes and consolidates the declarations at the top of their scope. The reassignment of `myVar` to `2` happens after the initial assignment, leading to the final value of `2` being logged. Below is how the JavaScript engine interprets the code as if it's written like this:

```javascript
var myVar;
myVar = 1;
console.log(myVar);

myVar = 2;
console.log(myVar);
```

<a name="Function_Hoisting"></a>
## Function Hoisting
In JavaScript, functions are subject to hoisting much like variables are. However, how function hoisting works depends on how the function is declared. 


**Example 4: Function Declaration Hoisting**

Function declarations are fully hoisted. This means that the entire body of the function, along with its declaration, is hoisted to the top of its scope. Here's how it works:

```javascript
console.log(myFunc()); // Output: "Some text!"

function myFunc() {
  return "Some text!";
}
```


**Example 5: Function Expression Hoisting**

Function expressions behave differently. If a function is assigned to a variable, the variable declaration is hoisted but not the assignment. This distinction is crucial for understanding how different function declaration methods behave regarding hoisting.

Consider this example using a function expression with `var`:

```javascript
console.log(myFunc); // Output: undefined
console.log(myFunc()); // TypeError: myFunc is not a function

var myFunc = function() {
  return "Some text!";
};
```

Here, the variable `myFunc ` is hoisted to the top of its scope, meaning it exists and is `undefined` when we try to log it or call it. This results in a TypeError when attempting to invoke `myFunc` as a function before the function expression is assigned to it.

For function expressions using `let` or `const`, an attempt to use them before declaration will lead to a ReferenceError because `let` and `const` declarations are in a TDZ as described earlier.

<a name="Class_Hoisting"></a>
## Class Hoisting

Classes in JavaScript exhibit the same hoisting behavior to variables declared with `let` and `const`. When you declare a class, its declaration is hoisted to the top of its enclosing scope, much like `let` and `const`. However, just as `let` and `const` declarations are not initialized until the JavaScript engine executes their declaration line, classes too are not initialized until the engine evaluates their declaration. This also results in a TDZ, meaning any attempts to access the class before its declaration will result in a `ReferenceError`. So, dealing with inheritance, static methods, and other object-oriented programming constructs, ensure that base classes are defined before derived classes try to extend them, respecting the TDZ.

<a name="Type_Alias"></a>
## Type Alias & Interface "Hoisting" in TypeScript

TypeScript's type aliases and interfaces are purely a design-time construct, meaning they are used by the TypeScript compiler for type checking and are erased when the TypeScript code is compiled to JavaScript. So, the concept of hoisting doesn’t really apply to type aliases or interfaces because they do not exist in the compiled output and, therefore, have no impact on the runtime behavior of the code.

For example:

```typescript
let myVar: MyType = { key: "value" };

type Animal = {
    key: string;
};
```

In the code above, even though the `MyType ` type is used before its declaration, TypeScript does not throw an error. This is because, from the perspective of the TypeScript type checking system, all type declarations are considered to be known ahead of any value-space code execution or evaluation. This isn't hoisting in the traditional JavaScript sense, but rather a feature of TypeScript's static analysis that all type declarations are globally known to the compiler before any code execution.

<a name="Tricky_Tasks"></a>
## Tricky Tasks
**Task 1.**
```javascript
var myVar = 1;

var myFunc = function() {
    console.log(myVar); // Output: ?

    var myVar = 2; 

    console.log(myVar); // Output: ?
}

myFunc();
```

**Task 2.**
```javascript
function myFunc() {
    return 1;
}
console.log(myFunc()); // Output: ?

function myFunc() {
    return 2;
}
console.log(myFunc()); // Output: ?
```

**Task 3.**
```javascript
(function() {
    try {
        throw new Error();
    } catch (x) {
        var x = 1, y = 2;
        console.log(x); // Output: ?
    }
    console.log(x); // Output: ?
    console.log(y); // Output: ?
})();
```

<a name="Solutions"></a>
## Solutions

**Task 1.**
- When `myFunc` is invoked, JavaScript hoists the declaration of `myVar` to the top of the function scope. This means the code inside `myFunc` behaves as if it was written like this:

```javascript
var myVar = undefined;

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

myVar = 2;

console.log(myVar); // Output: 2
```

- Therefore, the first `console.log(myVar)` outputs `undefined` because the declaration (but not the initialization) of `myVar` is hoisted to the top of the function scope, overshadowing the `myVar` declared outside the function scope.
- After `myVar` is assigned the value of `2` within the function, the second `console.log(myVar)` outputs `2`.

**Task 2.**

- Both function declarations are hoisted to the top of their scope. However, the second declaration of `myFunc` overwrites the first. This is because in JavaScript, function declarations are hoisted above variable declarations and are processed before the code begins to run.
- Therefore, regardless of where the functions are declared, the version of `myFunc` that finally resides in memory after hoisting and interpretation is the one that returns `2`.
- Both `console.log(myFunc());` calls will output `2`, because that's the version of `myFunc` being used after hoisting and overwriting.

**Task 3.**

- In the `catch` block, variables `myVar1` and `myVar2` are declared. Since `var` declarations are function-scoped (not block-scoped like `let` or `const`), both `myVar1` and `myVar2` are hoisted to the top of their enclosing function.
- Therefore, `console.log(myVar1);` within the `catch` block outputs `1`.
- Outside the `catch` block but inside the self-invoking function, both `myVar1` and `myVar2` are accessible due to hoisting, and since they were assigned values in the `catch` block, their values are preserved outside of the catch block.
- `console.log(myVar1);` outside the `catch` block also outputs `1`.
- Similarly, `console.log(myVar2);` outputs `2`.