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ável | Valor |
---|---|
nome | 'João' |
idade | 25 |
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ável | Valor | Endereço | Objeto |
---|---|---|---|
frutas | H001 | H001 | [] |
Variável | Valor | Endereço | Objeto |
---|---|---|---|
frutas | H001 | H001 | ['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ável | Valor | Endereço | Objeto |
---|---|---|---|
frutas | H001 | H001 | ['Banana'] |
frutasAmarelas | H001 |
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ável | Valor | Endereço | Objeto |
---|---|---|---|
frutas | H001 | H001 | ['Banana', 'Abacaxi'] |
frutasAmarelas | H001 |
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ável | Valor | Endereço | Objeto |
---|---|---|---|
pessoa | H002 | H001 | { 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ável | Valor | Endereço | Objeto |
---|---|---|---|
nota | 10 | ||
media | 2 | ||
dividir | H001 | H001 | function(x,y) |
x | 10 | ||
y | 2 |
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
- Tipos Primitivos: Armazenam o valor diretamente na variável.
- Tipos Não Primitivos: Armazenam uma referência ao valor, não o valor em si.
- Cópia de Referências: Objetos compartilhando a mesma referência refletem mudanças mutuamente.
- Operador
===
: Compara referências, não valores, em tipos não primitivos. - Funções Puras: Não alteram valores fora de seu escopo.
- Funções Impuras: Podem modificar o estado de objetos compartilhados por referência.
- 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!