Diferenças entre const, let e var!

Durante o desenvolvimento de uma aplicação com JavaScript, utilizamos variáveis para armazenas os dados.

Muitas vezes, criamos as variáveis de forma automática de acordo com a experiencia que tivemos em outros projetos. Sabemos que em um determinado contexto, devemos usar uma const e em outros devemos usar let.

É importante conhecer cada variável e suas diferenças, para que possamos utilizar aquela que faça mais sentido em cada parte do nosso código.

Atualmente, o JavaScript possui 4 tipos de variáveis.

  • const
  • let
  • var
  • não declarada

Neste artigo, vamos passar por cada uma delas, com exemplos e explicações. Vamos entender como elas são diferentes uma das outras e quando devemos usar cada uma!

ESCOPOS

Cada variável pode ter um tipo de escopo, dependendo de onde ela é declarada.

Atualmente, o JavaScript possui 3 tipos de escopo:

  • Escopo Global
  • Escopo de Função
  • Escopo de Bloco (introduzido na versão ES6 – 2015)

Fazendo um breve resumo dos tipos de escopo que cada variável pode ter:

  • var: Escopo Global – Escopo de Função;
  • let: Escopo Global – Escopo de Função – Escopo de Bloco;
  • const: Escopo Global – Escopo de Função – Escopo de Bloco;
  • não declarada: Escopo Global;

HOISTING (elevação)

O hoisting pode ser um pouco confuso de entender, mas em termos teóricos, é como se, ao declarar uma variável, essa declaração fosse movida para o inicio do escopo em que foi declarada.

Por exemplo, a declaração de uma variável no escopo global, fará com ela que ela seja movida para o inicio do código enquanto a declaração de uma variável dentro de uma função, fará com que ela seja movida para o inicio da função, pois ela pertence ao escopo da função.

Um ponto importante sobre o hoisting, é que ele só move as declarações. As inicializações não são movidas. Mas não se preocupe, vamos entender melhor como isso afeta cada declaração.

É comum dizer que a variável está na Temporal Dead Zone (TDZ) desde o inicio do código até que sua declaração seja processada.

Por último, o que acontece na prática, é que as declarações são guardadas em memória durante a fase de compilação e permanecem no mesmo lugar que foram colocadas no código.

VAR

Variáveis declaradas com var, podem ter Escopo Global ou Escopo de Função.


Escopo Global

Sempre que uma var for declarada do lado de fora de uma function, ela pertence ao Escopo Global e pode ser acessada em qualquer parte do código.

var fruta = "Abacaxi"; // Escopo Global

function x() {
  console.log(fruta); // Abacaxi
}

x();

console.log(fruta); // Abacaxi

No contexto de um navegador, ao pertencer ao Escopo Global, uma var se torna propriedade do Objeto Global window.

var nome = "João"; // Escopo Global

function x() {
  var fruta = "Maça"; // Escopo de Função
}

x();

console.log(window.nome); // João
console.log(window.fruta); // undefined

Escopo de Função

Sempre que uma var for declarada dentro de uma function, ela pertence ao Escopo de Função e não pode ser acessada do lado de fora.

function x() {
  var fruta = "Abacaxi"; // Escopo de Função

  console.log(fruta); // Abacaxi
}

x();

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

Escopo de Bloco

Uma variável declarada com var NÃO pode ter Escopo de Bloco. Quando ela é encontrada dentro de um bloco, sempre terá o Escopo Global ou Escopo de Função, dependendo de onde foi declarada.

var cor = "Verde"; // Escopo Global

if (true) {
  var fruta = "Pêssego"; // Escopo Global
}

function x() {
  var animal = "Gato"; // Escopo de Função

  if (true) {
    var fruta = "Pêssego"; // Escopo de Função
  }
}

x();

Hoisting

As declarações de var sempre serão processadas antes da execução de qualquer código.

Ou seja, ao declarar uma var, o hoisting eleva sua declaração para o inicio de seu escopo e a inicializa com o valor de undefined.

Este código:

var cor = "Amarela";

console.log(cor); // Amarela

Corresponde indiretamente a:

var cor;
cor = "Amarela";

console.log(cor); // Amarela

Da mesma forma que este código:

function x() {
  var cor = "Amarela";

  console.log(cor); // Amarela
}

x();

Corresponde indiretamente a:

function x() {
  var cor;
  cor = "Amarela";

  console.log(cor); // Amarela
}

x();

A inicialização da variável não é elevada. Somente a declaração.

Devido a esse comportamento, o valor undefined é atribuído à variável.

Este código:

console.log(cor); // undefined

var cor = "Amarela";

console.log(cor); // Amarela

Corresponde indiretamente a:

var cor = undefined;

console.log(cor); // undefined

cor = "Amarela";

console.log(cor); // Amarela

Da mesma forma que esse código:

cor = "Azul";

console.log(cor); // Azul

var cor = "Amarela";

console.log(cor); // Amarela

Corresponde indiretamente a:

var cor = undefined;

cor = "Azul";

console.log(cor); // Azul

var cor = "Amarela";

console.log(cor); // Amarela

Redeclaração

Uma variável var pode ser redeclarada.

var animal = "Gato";
console.log(animal); // Gato

var animal = "Cachorro";
console.log(animal); // Cachorro

Atribuição Opcional

Podemos declarar uma var sem atribuir um valor inicial.

var fruta;

fruta = "Abacaxi";

Reatribuição

Utilizar var nos permite reatribuir um valor sempre que necessário.

Por exemplo:

var contador = 100;

console.log(contador); // 100

if (true) {
  contador = 0;
}

console.log(contador); // 0

LET

Variáveis declaradas com let, podem ter Escopo Global, Escopo de Função ou Escopo de Bloco.

Escopo Global

Sempre que uma let for declarada do lado de fora de uma function ou bloco ({ }), ela pertencerá ao Escopo Global e pode ser acessada em qualquer parte do código.

let nome = "Pedro";

function x() {
  console.log(nome); // Pedro
}

x();

console.log(nome); // Pedro

Uma let NÃO pertence ao objeto global.

Escopo de Função

Sempre que uma let for declarada dentro de uma function, ela pertence ao Escopo de Função e não pode ser acessada do lado de fora.

function x() {
  let fruta = "Abacaxi"; // Escopo de Função

  console.log(fruta); // Abacaxi
}

x();

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

Escopo de Bloco

Sempre que uma let for declarada dentro de um bloco { }, ela pertence ao Escopo de Bloco e não pode ser acessada do lado de fora.

function x() {
  let animal = "Gato"; // Escopo de Função

  if (true) {
    let fruta = "Pêssego"; // Escopo de Bloco

    console.log(fruta); // Pêssego
  }

  console.log(animal); // Gato
  console.log(fruta); // ReferenceError: fruta is not defined
}

x();

Observe outros exemplos:

for (let index = 1; index <= 10; index++) {
  console.log(index); // de 1 até 10
}

console.log(index); // ReferenceError: index is not defined
if (true) {
  let cor = "Verde";
  console.log(cor); // Verde
} else {
  let cor = "Laranja";
  console.log(cor); // Laranja
}

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

Hoisting

Ao declarar uma let, o hoisting eleva sua declaração e NÃO a inicializa até que ela sai Temporal Dead Zone. Por conta disso, não é possível acessar uma let antes de sua inicialização.

console.log(nome);

let nome = "João"; // ReferenceError: Cannot access 'nome' before initialization

No caso da declaração SEM inicialização, o valor undefined é atribuído.

let nome;

console.log(nome); // undefined

Redeclaração

Uma variável let não pode ser redeclarada.

let cor = "Azul";

let cor = "Verde"; // SyntaxError: Identifier 'cor' has already been declared

Atribuição Opcional

Podemos declarar uma let sem atribuir um valor inicial.

let fruta;

fruta = "Abacaxi";

Reatribuição

Utilizar let nos permite reatribuir um valor sempre que necessário.

Por exemplo:

let contador = 100;

console.log(contador); // 100

if (true) {
  contador = 0;
}

console.log(contador); // 0

CONST

Variáveis declaradas com const, podem ter Escopo Global, Escopo de Função ou Escopo de Bloco.

Escopo Global

Sempre que uma const for declarada do lado de fora de uma function ou bloco ({ }), ela pertencerá ao Escopo Global e pode ser acessada em qualquer parte do código.

const nome = "Pedro";

function x() {
  console.log(nome); // Pedro
}

x();

console.log(nome); // Pedro

Uma const NÃO pertence ao objeto global.

Escopo de Função

Sempre que uma const for declarada dentro de uma function, ela pertence ao Escopo de Função e não pode ser acessada do lado de fora.

function x() {
  const fruta = "Abacaxi"; // Escopo de Função

  console.log(fruta); // Abacaxi
}

x();

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

Escopo de Bloco

Sempre que uma let for declarada dentro de um bloco { }, ela pertence ao Escopo de Bloco e não pode ser acessada do lado de fora.

function x() {
  const animal = "Gato"; // Escopo de Função

  if (true) {
    const fruta = "Pêssego"; // Escopo de Bloco

    console.log(fruta); // Pêssego
  }

  console.log(animal); // Gato
  console.log(fruta); // ReferenceError: fruta is not defined
}

x();

Observe outros exemplos:

Tanto o if como o else são considerados blocos diferentes:

if (true) {
  const cor = "Verde";
  console.log(cor); // Verde
} else {
  const cor = "Laranja";
  console.log(cor); // Laranja
}

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

Neste caso, as declarações feitas estão dentro do escopo de bloco do switch:

const cor = "Rosa";

switch (cor) {
  case "Azul":
    const cor = "Azul";
    console.log(cor);
    break;

  case "Verde":
    const cor = "Verde"; // SyntaxError: Identifier 'cor' has already been declared
    console.log(cor); 
    break;

  default:
    console.log("Cor " + cor + " não encontrada.");
}

Uma maneira de contornar esta situação, é adicionando o escopo de bloco { } para cada case:

const cor = "Rosa";

switch (cor) {
  case "Azul": {
    const cor = "Azul";
    console.log(cor); // Azul
    break;
  }

  case "Verde": {
    const cor = "Verde";
    console.log(cor); // Verde
    break;
  }

  default: {
    console.log("Cor " + cor + " não encontrada."); // Cor Rosa não encontrada.
  }
}

Hoisting

Ao declarar uma const, o hoisting eleva sua declaração e NÃO a inicializa até que ela sai Temporal Dead Zone. Por conta disso, não é possível acessar uma const antes de sua inicialização.

console.log(nome); // ReferenceError: Cannot access 'nome' before initialization

const nome = "João";

Redeclaração

Uma const não pode ser redeclarada.

const nome = "João";

const nome = "Jean"; // SyntaxError: Identifier 'nome' has already been declared

Atribuição Obrigatória

Ao declarar uma const, é obrigatório a atribuição de um valor.

const nome; // SyntaxError: Missing initializer in const declaration

Reatribuição

Uma const não pode ser reatribuída.

Isso ocorre porque é criada uma referencia imutável ao valor. Ou seja, você não pode alterar o valor que essa variável referencia.

const pi = 3.14159265358979323846;

pi = 3.14; // TypeError: Assignment to constant variable

Neste exemplo, a declaração da const cria uma referencia de pi para o valor 3.14159265358979323846.

Quando tentamos reatribuir o valor de pi para 3.14, estamos tentando alterar a referencia de pi.