↔️

== vs ===: Double Equals and Coercion

Sabemos que existem dois comparadores de igualdade no Javascript: == e ===, ou como são chamados: double equals e triple equals.
Vamos dissecar aqui o funcionamento desses dois operadores e entender se um é melhor que o outro.
As mesmas regras explicadas são válidas para os comparadores de desigualdade != e !==.

Triple Equals

Tecnicamente chamado de strict equality comparison (comparação de igualdade estrita) retorna true caso os dois operandos sejam iguais. Esse operador usa o algoritmo de comparação de igualdade estrita. Entende-se por iguais aqueles que são do mesmo tipo e possuem o mesmo valor.
  • Se os operandos foram de tipos diferentes, retorna
1 === '1' // false
O primeiro operando tem o tipo Number, e o segundo, String
  • Se ambos os operandos forem um objeto, retorna true apenas se eles se referem ao mesmo objeto
const first = { b: 'b' } const second = first first === second // true { a: 'a' } === { a: 'a' } // false
No segundo exemplo, apesar de ambos os objetos serem aparentemente iguais, eles foram criados em endereços de memória distintos, logo, do ponto de vista de igualdade estrita, são diferentes - ver
🧩
Value Types and Reference Types
  • Se ambos os operandos forem null ou undefined, retorna true
null === null // true undefined === undefined // true
  • Se ambos os operandos forem NaN, retorna false
  • Caso contrário, os valores dos operandos são comparados:
    • Números devem ter o mesmo valor numérico. +0 e -0 são considerados mesmo valor.
    • Strings devem ter os mesmos caracteres e na mesma ordem
    • Booleans devem ser ambos true ou false

Double Equals

Tecnicamente chamado de abstract equality comparison (comparação de igualdade abstrata), e também conhecido como loose equality, diferentemente do triple equals, esse comparador tenta converter operandos de diferentes tipos antes de efetuar a comparação.
Esse operador usa o algoritmo de comparação abstrata (ou ver ES6) que vamos detalhar abaixo.
Vamos considerar que estamos comparando x e y, onde ambos podem ser de qualquer tipo.
  • Caso os operandos forem do mesmo tipo, o Javascript irá simplesmente usar o operador triple equals. Logo 2 == 2 é o mesmo de 2 === 2 pois ambos são números (tipo Number).
Porém, caso sejam de tipos diferentes, a regra para decidir qual operando converter depende dos tipos do operando.
  • Se ambos os operandos forem null ou undefined, retorna true
  • Se estivermos comparando uma String com um Number, a string é convertida para número e então o operador é chamado novamente.
1 == '1' // 1 == ToNumber('1') // 1 == 1 // true
  • Se estivermos comparando um Boolean com qualquer outro tipo, o boolean é convertido para número e então o operador é chamado novamente
true == 1 // ToNumber(true) == 1 // 1 == 1 // true true == 2 // ToNumber(true) == 2 // 1 == 2 // false
O resultado do primeiro exemplo é true pois a conversão de um Boolean true para número é sempre igual 1. Por outro lado false é igual a +0
  • Se um dos operandos for do tipo String, Number ou Symbol, e o outro for Object, o objeto é convertido para primitivo e então o operador é chamado novamente
{ a: 'hello' } == '5' // ToPrimitive({ a: 'hello' }) == '5' // '[object Object]' == '5' // false [1,2,3] == '5' // ToPrimitive([1,2,3]) == '5' // '1,2,3' == '5' // false
Caso a comparação não se encaixe em nenhum cenário acima, retorna false

Objects To Primitive

Ao converter objetos para primitivos, dois métodos podem ser usados: valueOf e toString. Por padrão, o método valueOf do objeto é usado primeiro. Caso retorne um primitivo - ver
🐵
Primitives
- esse valor será usado.
Uma segunda tentativa será realizada, dessa vez com o método toString. Caso retorne um primitivo, esse valor será usado.
Caso nenhum dos dois métodos retorne um valor primitivo, uma exceção será disparada.
const object = { a: 'hello' } object == '1' // ToPrimitive(object) == '1' // object.valueOf() // Retorna um objeto {} // object.toStirng() // retorna uma string '[object Object]' // '[object Object]' == '1' // false
const object = { a: 'hello' } object.toString = () => ({}) object == '1' // ToPrimitive(object) == '1' // object.valueOf() // Retorna um objeto {} // object.toStirng() // Retorna um objeto {} pois alteramos o método
O exemplo acima irá disparar uma exceção, pois nenhum dos dois métodos (valueOf e toString) retornou um primitivo.
notion image
Para objetos do tipo Date, a ordem dos métodos é toString e valueOf

Conclusão

Com todas essas variáveis a se considerar, é melhor usarmos sempre o operador ===, pois não precisamos nos preocupar com tantas regras e observações, fora que seu uso torna a leitura e entendimento mais natural.

Referências