[Medium] š var, let, const
VisĆ£o geralā
Em JavaScript, existem três palavras-chave para declarar variÔveis: var, let e const. Embora todas sejam usadas para declarar variÔveis, elas diferem em escopo, inicialização, redeclaração, reatribuição e momento de acesso.
Principais diferenƧasā
| Comportamento | var | let | const |
|---|---|---|---|
| Escopo | Função ou global | Bloco | Bloco |
| Inicialização | Opcional | Opcional | Obrigatória |
| Redeclaração | Permitida | Não permitida | Não permitida |
| Reatribuição | Permitida | Permitida | Não permitida |
| Acesso antes da declaração | Retorna undefined | Lança ReferenceError | Lança ReferenceError |
Explicação detalhadaā
Escopoā
O escopo de var é de função ou global, enquanto let e const têm escopo de bloco (incluindo funções, blocos if-else ou loops for).
function scopeExample() {
var varVariable = 'var';
let letVariable = 'let';
const constVariable = 'const';
console.log(varVariable); // 'var'
console.log(letVariable); // 'let'
console.log(constVariable); // 'const'
}
scopeExample();
console.log(varVariable); // ReferenceError: varVariable is not defined
console.log(letVariable); // ReferenceError: letVariable is not defined
console.log(constVariable); // ReferenceError: constVariable is not defined
if (true) {
var varInBlock = 'var in block';
let letInBlock = 'let in block';
const constInBlock = 'const in block';
}
console.log(varInBlock); // 'var in block'
console.log(letInBlock); // ReferenceError: letInBlock is not defined
console.log(constInBlock); // ReferenceError: constInBlock is not defined
Inicializaçãoā
var e let podem ser declarados sem inicialização, enquanto const deve ser inicializado no momento da declaração.
var varVariable; // VƔlido
let letVariable; // VƔlido
const constVariable; // SyntaxError: Missing initializer in const declaration
Redeclaraçãoā
Dentro do mesmo escopo, var permite a redeclaração da mesma variÔvel, enquanto let e const não permitem.
var x = 1;
var x = 2; // VƔlido, x agora Ʃ 2
let y = 1;
let y = 2; // SyntaxError: Identifier 'y' has already been declared
const z = 1;
const z = 2; // SyntaxError: Identifier 'z' has already been declared
Reatribuiçãoā
VariĆ”veis declaradas com var e let podem ser reatribuĆdas, mas variĆ”veis declaradas com const nĆ£o podem.
var x = 1;
x = 2; // VƔlido
let y = 1;
y = 2; // VƔlido
const z = 1;
z = 2; // TypeError: Assignment to a constant variable
Observação: Embora uma variĆ”vel declarada com const nĆ£o possa ser reatribuĆda, se for um objeto ou array, seu conteĆŗdo ainda pode ser modificado.
const obj = { key: 'value' };
obj.key = 'new value'; // VƔlido
console.log(obj); // { key: 'new value' }
const arr = [1, 2, 3];
arr.push(4); // VƔlido
console.log(arr); // [1, 2, 3, 4]
Acesso antes da declaração (Temporal Dead Zone)ā
VariÔveis declaradas com var são elevadas e automaticamente inicializadas como undefined. VariÔveis declaradas com let e const também são elevadas, mas não são inicializadas. Acessar antes da declaração lança um ReferenceError.
console.log(x); // undefined
var x = 5;
console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 5;
console.log(z); // ReferenceError: Cannot access 'z' before initialization
const z = 5;
Perguntas de entrevistaā
Pergunta: A armadilha clĆ”ssica do setTimeout + varā
Determine o resultado de saĆda do seguinte código:
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}
Resposta incorreta (equĆvoco comum)ā
Muitas pessoas pensam que a saĆda Ć©: 1 2 3 4 5
SaĆda realā
6
6
6
6
6
Por quĆŖ?ā
Este problema envolve trĆŖs conceitos fundamentais:
1. O escopo de função do var
// var não cria um escopo de bloco dentro do loop
for (var i = 1; i <= 5; i++) {
// i estƔ no escopo externo, todas as iteraƧƵes compartilham o mesmo i
}
console.log(i); // 6 (valor de i após o fim do loop)
// No caso do var
{
var i;
i = 1;
i = 2;
i = 3;
i = 4; // loop encerrado
}
2. A execução assĆncrona do setTimeout
// setTimeout Ć© assĆncrono, executa após o código sĆncrono atual terminar
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
// Este código é colocado na fila de tarefas do Event Loop
console.log(i);
}, 0);
}
// O loop executa primeiro (i se torna 6), depois os callbacks do setTimeout comeƧam a executar
3. ReferĆŖncia do Closure
// Todas as funƧƵes callback do setTimeout referenciam o mesmo i
// Quando os callbacks executam, i jĆ” Ć© 6
SoluƧƵesā
Solução 1: Usar let (recomendado) ā
for (let i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}
// SaĆda: 1 2 3 4 5
// No caso do let
{
let i = 1; // i da primeira iteração
}
{
let i = 2; // i da segunda iteração
}
{
let i = 3; // i da terceira iteração
}
PrincĆpio: let cria um novo escopo de bloco a cada iteração, e cada callback setTimeout captura o valor de i da iteração atual.
// Equivalente a
{
let i = 1;
setTimeout(function () {
console.log(i);
}, 0);
}
{
let i = 2;
setTimeout(function () {
console.log(i);
}, 0);
}
// ... e assim por diante
Solução 2: Usar IIFE (Expressão de Função Imediatamente Invocada)
for (var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(function () {
console.log(j);
}, 0);
})(i);
}
// SaĆda: 1 2 3 4 5
PrincĆpio: A IIFE cria um novo escopo de função, e em cada iteração, o valor atual de i Ć© passado como parĆ¢metro j, formando um Closure.
Solução 3: Usar o terceiro parâmetro do setTimeout
for (var i = 1; i <= 5; i++) {
setTimeout(
function (j) {
console.log(j);
},
0,
i
); // O terceiro parâmetro é passado para a função callback
}
// SaĆda: 1 2 3 4 5
PrincĆpio: O terceiro parĆ¢metro Ć© os seguintes do setTimeout sĆ£o passados como argumentos para a função callback.
Solução 4: Usar bind
for (var i = 1; i <= 5; i++) {
setTimeout(
function (j) {
console.log(j);
}.bind(null, i),
0
);
}
// SaĆda: 1 2 3 4 5
PrincĆpio: bind cria uma nova função e vincula o valor atual de i como parĆ¢metro.
Comparação de soluƧƵesā
| Solução | Vantagens | Desvantagens | Recomendação |
|---|---|---|---|
let | Conciso, moderno, fƔcil de entender | ES6+ | 5/5 Altamente recomendado |
| IIFE | Boa compatibilidade | Sintaxe complexa | 3/5 Pode ser considerado |
| Parâmetro setTimeout | Simples e direto | Pouco conhecido | 4/5 Recomendado |
bind | Estilo funcional | Legibilidade um pouco menor | 3/5 Pode ser considerado |
Perguntas adicionaisā
Q1: E se mudarmos para isso?
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
Resposta: 6 Ć© impresso uma vez por segundo, totalizando 5 vezes (respectivamente a 1, 2, 3, 4 e 5 segundos).
Q2: Como imprimir sequencialmente 1, 2, 3, 4, 5 a cada segundo?
for (let i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
// Após 1 segundo: 1
// Após 2 segundos: 2
// Após 3 segundos: 3
// Após 4 segundos: 4
// Após 5 segundos: 5
Pontos-chave em entrevistasā
Esta pergunta avalia:
- Escopo de var: Escopo de função vs escopo de bloco
- Event Loop: Execução sĆncrona vs assĆncrona
- Closure: Como as funƧƵes capturam variƔveis externas
- Soluções: Múltiplas abordagens com vantagens e desvantagens
Ao responder, Ć© recomendado:
- Primeiro dar a resposta correta (6 6 6 6 6)
- Explicar a razĆ£o (escopo do var + setTimeout assĆncrono)
- Fornecer soluƧƵes (preferir let e explicar outras opƧƵes)
- Demonstrar compreensão dos mecanismos internos do JavaScript
Melhores prĆ”ticasā
- Priorizar
const: Para variĆ”veis que nĆ£o precisam ser reatribuĆdas,constmelhora a legibilidade Ć© a manutenibilidade do código. - Em seguida usar
let: Quando a reatribuição é necessÔria, usarlet. - Evitar
var: Como o escopo é o comportamento de Hoisting dovarpodem causar problemas inesperados, é recomendado evitÔ-lo no desenvolvimento JavaScript moderno. - Atenção à compatibilidade do navegador: Se for necessÔrio suportar navegadores antigos, ferramentas como Babel podem transpilar
leteconstparavar.