Pular para o conteĆŗdo principal

[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​

Comportamentovarletconst
EscopoFunção ou globalBlocoBloco
InicializaçãoOpcionalOpcionalObrigatória
RedeclaraçãoPermitidaNão permitidaNão permitida
ReatribuiçãoPermitidaPermitidaNão permitida
Acesso antes da declaraçãoRetorna undefinedLança ReferenceErrorLanç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çãoVantagensDesvantagensRecomendação
letConciso, moderno, fƔcil de entenderES6+5/5 Altamente recomendado
IIFEBoa compatibilidadeSintaxe complexa3/5 Pode ser considerado
Parâmetro setTimeoutSimples e diretoPouco conhecido4/5 Recomendado
bindEstilo funcionalLegibilidade um pouco menor3/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:

  1. Escopo de var: Escopo de função vs escopo de bloco
  2. Event Loop: Execução síncrona vs assíncrona
  3. Closure: Como as funƧƵes capturam variƔveis externas
  4. 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​

  1. Priorizar const: Para variÔveis que não precisam ser reatribuídas, const melhora a legibilidade é a manutenibilidade do código.
  2. Em seguida usar let: Quando a reatribuição é necessÔria, usar let.
  3. Evitar var: Como o escopo Ć© o comportamento de Hoisting do var podem causar problemas inesperados, Ć© recomendado evitĆ”-lo no desenvolvimento JavaScript moderno.
  4. Atenção à compatibilidade do navegador: Se for necessÔrio suportar navegadores antigos, ferramentas como Babel podem transpilar let e const para var.

Tópicos relacionados​