O que é Hoisting no JavaScript?

Em resumo, segundo a MDN, Hoisting é o processo de elevar declarações de variáveis, funções e classes para o topo de seu escopo. Essa funcionalidade foi introduzida no ES6 com a finalidade de permitir que esses recursos possam ser acessados de forma segura antes de sua declaração.

É graças à essa funcionalidade que o seguinte código funciona:

console.log(age);

var age = 20;

Se as declarações não fossem elevadas, o seguinte código emitiria um erro: Uncaught ReferenceError: age is not defined.

Voltando ao exemplo acima, um detalhe importante à se destacar é que o JavaScript aplica Hoisting apenas às declarações, ou seja, o valor de inicialização não é considerado, logo o resultado do console.log do exemplo acima é o seguinte:

console.log(age); // undefined

var age = 20;

Vamos observar o antes e depois do processo de hoisting de um outro exemplo:

var name = 'giga';

console.log(name);

Após o Hoisting teremos a seguinte sequência: Declaração, atribuição e a execução do console.log:

var name; 
name = 'giga'; 

console.log(name); 

Hoisting de variáveis

Observamos como o processo de Hoisting funciona com var, porém se tentarmos algo semelhante utilizando const ou let notaremos um comportamento diferente:

console.log(name); // ReferenceError: name is not defined

let name = 'giga';

Assim como variáveis declaradas com var, os demais tipos (const e let) também sofrem o processo de Hoisting, porém, diferente do que acontece quando utilizamos var, as variáveis não são inicializadas com um valor padrão (undefined no caso do var). Então, apesar da declaração ser movida para o topo do escopo, um erro será lançado caso a variável seja lida antes de sua inicialização.

Ou seja, nesses casos, a variável só poderá ser utilizada após sua declaração. O espaço em que a variável está indisponível (Inicio do bloco até a sua inicialização) é chamada de Temporal Dead Zone (TDZ).

console.log(name); // ReferenceError: name is not defined

let name = 'giga'; // Final da TDZ

Hoisting de funções

O processo de Hoisting também se aplica à declaração de funções, ou seja, é possível utilizar funções antes mesmo de declará-las:

console.log(sum(1, 1)); // 2

function sum(num1, num2) {
   return num1 + num2;
}

Um ponto importante é que function expressions não sofrem Hoisting, o que significa que não é possível utilizar uma função anônima antes de sua declaração:

console.log(sum(1, 2)); // ReferenceError: sum is not defined

const sum = (num1, num2) => num1 + num2;

Nesse caso, o hoisting é aplicado à const sum e, como vimos, apesar de sofrerem Hoisting, elas só ficam disponíveis depois de sua inicialização. As regras que abordamos para var e let também se aplicam nesse cenário:

console.log(sum(1, 2)); // ReferenceError: sum is not defined

let sum = (num1, num2) => num1 + num2;

// -------------------

console.log(sum(1, 2)); // TypeError: sum is not a function

var sum = (num1, num2) => num1 + num2;

Hoisting de classes

No JavaScript, declarações de classes também sofrem Hoisting, porém, assim como variáveis let e const, elas não são inicializadas por padrão, o que significa que uma classe não poderá ser utilizada até ser inicializada e, como no tópico acima, class expressions não sofrem Hoisting.

const dog = new Dog('Cão'); // ReferenceError: Dog is not defined

class Dog {
   constructor (name) {
      this.name = name
   }
}

Referências

MDN Web Docs - Hoisting

MDN Web Docs - Temporal Dead Zone