Executando verificação de segurança...
10
thdr
5 min de leitura ·

Por que não conseguimos comparar arrays e objetos com ===?

Olá pessoal, decidi escrever esse artigo para tirar uma dúvida que sempre tive, que era o motivo pelo qual não conseguimos comparar arrays e objetos com ===. Então estudei um pouco e isso foi o que encontrei sobre como o Javascript funciona por debaixo dos panos.

Javascript tem 5 tipos de dados que são passados por valor: boolean, null, undefined, String e Number. São chamados de tipos primitivos

Javascript tem 3 tipos de dados que são passados por referência: Array, Function e Object. Tecnicamente, todos os 3 são objetos, então podemos nos referir aos 3 como objetos. São chamados de tipos não primitivos

Tipos Primitivos (Valor)

Quando você declara uma variável de um tipo primitivo, ela armazena o valor do tipo.

const nome = 'João'
const idade = 25
VariávelValor
nome'João'
idade25

Simples assim.

Tipos Não Primitivos (Referência)

Variáveis que recebem um valor não primitivo são atribuídas a uma referência para esse valor. Essa referência aponta para a localização do objeto na memória. Nesse caso, a variável vai armazenar a referência, não o valor em si.

Por exemplo:

const frutas = []
frutas.push('Banana')

A representação do que acontece nessas linhas é o seguinte:

VariávelValorEndereçoObjeto
frutasH001H001[]
VariávelValorEndereçoObjeto
frutasH001H001['Banana']

O que acontece quando declaramos uma variável pela referência?

Quando uma variável de referência é copiada para outra usando =, estamos copiando o endereço de referência. Objetos são copiados pela referência ao invés do valor.

const frutas = ['Banana']
const frutasAmarelas = frutas
VariávelValorEndereçoObjeto
frutasH001H001['Banana']
frutasAmarelasH001

Então, se adicionarmos uma fruta no array de frutasAmarelas, estaremos adicionando também no array de frutas, já que eles compartilham a mesma referência.

frutasAmarelas.push('Abacaxi')
VariávelValorEndereçoObjeto
frutasH001H001['Banana', 'Abacaxi']
frutasAmarelasH001

Redeclarando uma variável de referência

Quando reassinamos uma variável de referência, ela substitui a referência e não o valor.

let pessoa = { nome: 'João' }
pessoa = { nome: 'Maria' }

Em memória, acontece isso:

VariávelValorEndereçoObjeto
pessoaH002H001{ nome: 'João' }
H002{ nome: 'Maria' }
A referência antiga ainda é mantida no endereço, porém substituímos o valor do endereço pelo novo.

Por que não é possível comparar arrays com ===?

Quando === é usado em um tipo de referência, isto é dados não primitivos, a condição só vai retornar verdadeiro se eles tiverem a mesma referência, porque é isso que o === checa em dados não primitivos, checa a referência, não o valor

const arr1 = ['1']
const arr2 = arr1

console.log(arr1 === arr2) // Verdadeiro

Porém, se eles forem objetos distintos, por mais que tenham o mesmo valor, as referências são diferentes, então a comparação retorna false

const arr1 = ['1']
const arr2 = ['2']

console.log(arr1 === arr2) // Falso

Parâmetros nas funções

Quando passamos valores primitivos por parâmetros, o comportamento é simples, ele assina o valor no parâmetro como se eu estivesse usando um =

const nota = 10
const media = 2

function dividir(x, y) {
	return x / y
}

const notaMedia = dividir(nota, media)

Nesse caso, nota é igual a 10, media é igual a 2, x é igual a 10, e y é igual a 2. Isso porque ele copiou o valor para a variável de parâmetro.

VariávelValorEndereçoObjeto
nota10
media2
dividirH001H001function(x,y)
x10
y2

Funções Puras

Funções puras são funções que não afetam nada fora do escopo dela mesma e todas as variáveis usando dentro dela são limpas assim que a função retorna o valor.

O exemplo de função acima dividir é uma função pura.

Funções Impuras

Funções impuras são funções que podem receber objetos e mudar o estado dele fora do escopo. Porque ela recebe a referência do valor, e caso essa referência seja alterada dentro da função, também será alterada fora da função. Por exemplo:

function trocarNome(pessoa) {
	pessoa.nome = 'Maria'
	return pessoa
}

const joao = {
	nome: 'João'
}

const maria = trocarNome(joao);

console.log(joao) // Maria
console.log(maria) // Maria

Isso acontece porque dentro da função, estamos alterando o valor que está na referência, que no caso é a mesma tanto para joao tanto para maria porque o parâmetro recebe a referência e não o valor.

Ok, mas como transformamos isso em uma função pura? Da mesma forma que o Array.map e o Array.filter faz. Criamos uma cópia do objeto dentro da função, manipulamos e retornamos essa cópia, assim não alteramos a referência original.

Refatorando então, ficaria dessa forma:

function trocarNome(pessoa) {
	const novaPessoa = JSON.parse(JSON.stringify(pessoa))
	novaPessoa.nome = 'Maria'
	return novaPessoa
}

const joao = {
	nome: 'João'
}

const maria = trocarNome(joao);


console.log(joao) // Joao
console.log(maria) // Maria

Nessa função, transformamos o objeto em uma string (tipo primitivo) e depois transformamos ele em um objeto novamente, essa operação faz com que uma nova referência seja criada, porque quando o objeto se torna string, ele se torna um valor primitivo e depois o objeto é criado a partir desse valor e não da referência do original.

Conclusão

  1. Tipos Primitivos: Armazenam o valor diretamente na variável.
  2. Tipos Não Primitivos: Armazenam uma referência ao valor, não o valor em si.
  3. Cópia de Referências: Objetos compartilhando a mesma referência refletem mudanças mutuamente.
  4. Operador ===: Compara referências, não valores, em tipos não primitivos.
  5. Funções Puras: Não alteram valores fora de seu escopo.
  6. Funções Impuras: Podem modificar o estado de objetos compartilhados por referência.
  7. Tornando funções puras: Criar cópias dos objetos evita alterações em referências originais.

E se quiser saber mais sobre como as variáveis são armazenadas no Javascript, leia este outro artigo que escrevi, explicando sobre Call Stack e Memory Heap.

Espero que tenha gostado, até breve!

Carregando publicação patrocinada...
2
1

Parabéns pelo artigo, muito bom!
Por mais que esteja anos trabalhando com uma ferramenta, volta e meia rever os conceitos fundamentais é muito importante e ajuda a evitar umas dores de cabeça na hora de desenvolver.