[Medium] 📄 Hoisting
1. What's Hoisting ?
A execução do JS pode ser dividida em duas fases: a fase de criação é a fase de execução:
var name = 'Pitt';
console.log(name); // print Pitt
Devido à característica do Hoisting, o código acima deve ser entendido como: primeiro a variável é declarada e depois o valor é atribuído.
// create
var name;
// execute
name = 'Pitt';
console.log(name);
As funções são diferentes das variáveis — elas são alocadas na memória durante a fase de criação. A declaração de função é a seguinte:
getName();
function getName() {
console.log('string'); // print string
}
O código acima consegue executar normalmente e imprimir o console.log sem gerar erro por causa da seguinte lógica: a function é primeiro elevada ao topo, e só depois a chamada da function é executada.
// create
function getName() {
console.log('string');
}
// execute
getName();
No entanto, é preciso notar que essa característica de Hoisting exige atenção à ordem de escrita ao usar expressões.
Na fase de criação, a function tem a maior prioridade, seguida pelas variáveis.
Correct
name = 'Yumy';
console.log(name); // print Yumy
var name;
// --- Equal to ---
// create
var name;
// execute
name = 'Yumy';
console.log(name); // print Yumy
Wrong
console.log(name); // print undefined
var name = 'Jane';
// --- Equal to ---
// create
var name;
// execute
console.log(name); // print undefined,pois o valor ainda não foi atribuído, apenas o undefined padrão é retornado
name = 'Pitt';
2. What's name printed ?
whoseName();
function whoseName() {
if (name) {
name = 'Nini';
}
}
var name = 'Pitt';
console.log(name);
Answer
// create
function whoseName() {
if (name) {
name = 'Nini';
}
}
var name;
// execute
whoseName();
name = 'Pitt';
console.log(name); // print Pitt
name em whoseName() recebe undefined, portanto a condição não é atendida.
Porém, como há outra atribuição abaixo da declaração de função, mesmo que a condição dentro da function fosse atendida, o resultado final séria a impressão de Pitt.
3. Declaração de função vs Declaração de variável: Prioridade do Hoisting
Questão: Função e variável com o mesmo nome
Determine o resultado de saída do seguinte código:
console.log(foo);
var foo = '1';
function foo() {}
Resposta incorreta (equívoco comum)
Muitas pessoas pensam que:
- A saída é
undefined(achando que var é elevado primeiro) - A saída é
'1'(achando que a atribuição tem impacto) - Ocorre um erro (achando que nomes iguais causam conflito)
Saída real
[Function: foo]
Por quê?
Esta questão examina as regras de prioridade do Hoisting:
Prioridade do Hoisting: Declaração de função > Declaração de variável
// Código original
console.log(foo);
var foo = '1';
function foo() {}
// Equivalente a (após Hoisting)
// Fase 1: Fase de criação (Hoisting)
function foo() {} // 1. Declaração de função elevada primeiro
var foo; // 2. Declaração de variável elevada (mas não sobrescreve a função existente)
// Fase 2: Fase de execução
console.log(foo); // Neste momento foo é uma função, saída [Function: foo]
foo = '1'; // 3. Atribuição de variável (sobrescreve a função)
Conceitos-chave
1. Declarações de função são completamente elevadas
console.log(myFunc); // [Function: myFunc]
function myFunc() {
return 'Hello';
}
2. Declarações de variável com var só elevam a declaração, não a atribuição
console.log(myVar); // undefined
var myVar = 'Hello';
3. Quando declaração de função e declaração de variável têm o mesmo nome
// Ordem após a elevação
function foo() {} // Função elevada primeiro é atribuída
var foo; // Declaração de variável elevada, mas não sobrescreve a função existente
// Portanto foo é uma função
console.log(foo); // [Function: foo]
Fluxo de execução completo
// Código original
console.log(foo); // ?
var foo = '1';
function foo() {}
console.log(foo); // ?
// ======== Equivalente a ========
// Fase de criação (Hoisting)
function foo() {} // 1. Declaração de função elevada (elevação completa, incluindo o corpo da função)
var foo; // 2. Declaração de variável elevada (mas não sobrescreve foo, pois já é uma função)
// Fase de execução
console.log(foo); // [Function: foo] - foo é uma função
foo = '1'; // 3. Atribuição de variável (somente agora sobrescreve a função)
console.log(foo); // '1' - foo se torna uma string
Exercícios avançados
Exercício A: Influência da ordem
console.log(foo); // ?
function foo() {}
var foo = '1';
console.log(foo); // ?
Resposta:
[Function: foo] // Primeira saída
'1' // Segunda saída
Razão: A ordem do código não afeta o resultado do Hoisting. A prioridade de elevação continua sendo: função > variável.
Exercício B: Múltiplas funções com o mesmo nome
console.log(foo); // ?
function foo() {
return 1;
}
var foo = '1';
function foo() {
return 2;
}
console.log(foo); // ?
Resposta:
[Function: foo] { return 2; } // Primeira saída (a função posterior sobrescreve a anterior)
'1' // Segunda saída (atribuição de variável sobrescreve a função)
Razão:
// Após a elevação
function foo() {
return 1;
} // Primeira função
function foo() {
return 2;
} // Segunda função sobrescreve a primeira
var foo; // Declaração de variável (não sobrescreve a função)
console.log(foo); // [Function: foo] { return 2; }
foo = '1'; // Atribuição de variável (sobrescreve a função)
console.log(foo); // '1'
Exercício C: Expressão de função vs Declaração de função
console.log(foo); // ?
console.log(bar); // ?
var foo = function () {
return 1;
};
function bar() {
return 2;
}
Resposta:
undefined; // foo é undefined
[Function: bar] // bar é uma função
Razão:
// Após a elevação
var foo; // Declaração de variável elevada (expressão de função só eleva o nome da variável)
function bar() {
return 2;
} // Declaração de função elevada completamente
console.log(foo); // undefined
console.log(bar); // [Function: bar]
foo = function () {
return 1;
}; // Atribuição da expressão de função
Diferença fundamental:
- Declaração de função:
function foo() {}→ elevada completamente (incluindo o corpo da função) - Expressão de função:
var foo = function() {}→ apenas o nome da variável é elevado, o corpo da função não
let/const não têm esse problema
// var tem problemas de elevação
console.log(foo); // undefined
var foo = '1';
// let/const têm Zona Morta Temporal (TDZ)
console.log(bar); // ReferenceError: Cannot access 'bar' before initialization
let bar = '1';
// let/const com mesmo nome de função causa erro
function baz() {} // SyntaxError: Identifier 'baz' has already been declared
let baz = '1';
Resumo da prioridade do Hoisting
Prioridade do Hoisting (da mais alta para a mais baixa):
1. Declaração de função (Function Declaration)
├─ function foo() {} ✅ elevação completa
└─ prioridade mais alta
2. Declaração de variável (Variable Declaration)
├─ var foo ⚠️ apenas a declaração é elevada, não a atribuição
└─ não sobrescreve funções existentes
3. Atribuição de variável (Variable Assignment)
├─ foo = '1' ✅ sobrescreve a função
└─ ocorre na fase de execução
4. Expressão de função (Function Expression)
├─ var foo = function() {} ⚠️ tratada como atribuição de variável
└─ apenas o nome da variável é elevado, não o corpo da função
Pontos-chave em entrevistas
Ao responder este tipo de pergunta, é recomendado:
- Explicar o mecanismo do Hoisting: Dividido em fase de criação e fase de execução
- Enfatizar a prioridade: Declaração de função > Declaração de variável
- Desenhar o código após o Hoisting: Mostrar ao entrevistador sua compreensão
- Mencionar as melhores práticas: Usar let/const para evitar problemas de Hoisting com var
Exemplo de resposta em entrevista:
"Esta questão examina a prioridade do Hoisting. Em JavaScript, a declaração de função tem prioridade de elevação maior que a declaração de variável.
O processo de execução se divide em duas fases:
- Fase de criação:
function foo() {}é completamente elevada ao topo, depois a declaraçãovar fooé elevada, mas não sobrescreve a função existente.- Fase de execução: Em
console.log(foo), foo é uma função neste momento, então[Function: foo]é exibido. Depois,foo = '1'sobrescreve foo com uma string.A melhor prática é usar
let/constno lugar devar, e colocar as declarações de função no topo para evitar esse tipo de confusão."