Coerção de tipos e comparação com == e ===

1. O que é coerção de tipos?

Coerção de tipos é o processo automático ou manual de converter um valor de um tipo para outro em JavaScript. Existem duas formas principais:

  • Coerção implícita: O JavaScript converte automaticamente os tipos durante operações. Exemplo: "5" - 3 resulta em 2 (string vira número).
  • Coerção explícita: O desenvolvedor intencionalmente converte usando funções como Number(), String() ou Boolean().

O JavaScript possui 7 tipos primitivos: string, number, boolean, null, undefined, symbol e bigint. Objetos (arrays, funções, datas) são tipos complexos. A coerção segue regras previsíveis, mas muitas vezes contra-intuitivas, sendo fonte clássica de bugs — especialmente quando combinada com o operador ==.

console.log(1 + "2");        // "12" (coerção implícita)
console.log(Number("42"));   // 42 (coerção explícita)
console.log(!!"texto");      // true (coerção explícita via !!

2. O operador == (igualdade abstrata)

O operador == aplica coerção automática antes da comparação, seguindo regras da especificação ECMAScript (Seção 7.2.14). As principais regras são:

  • Se os tipos são iguais, compara diretamente (como ===).
  • Se um é null e outro undefined, retorna true.
  • Se um é número e outro string, converte string para número.
  • Se um é booleano, converte para número (true → 1, false → 0).
  • Se um é objeto e outro primitivo, chama ToPrimitive() no objeto.
console.log(0 == false);      // true (false → 0)
console.log("" == 0);         // true ("" → 0)
console.log(null == undefined); // true (regra especial)
console.log("1" == true);     // true (true → 1, "1" → 1)

3. O operador === (igualdade estrita)

O === compara valor e tipo sem realizar coerção. É sempre mais previsível e recomendado na maioria dos casos.

console.log(0 === false);     // false (tipos diferentes)
console.log("" === 0);        // false
console.log(null === undefined); // false

No Node.js, === é especialmente importante para evitar bugs em validações de dados. Porém, existem casos onde === ainda surpreende:

console.log(NaN === NaN);     // false (NaN nunca é igual a nada)
console.log(-0 === +0);       // true (mas são diferentes em Object.is)
console.log(Object.is(NaN, NaN)); // true

4. Casos polêmicos e pegadinhas comuns

Alguns exemplos clássicos que mostram o comportamento bizarro do ==:

console.log([] == ![]);       // true (![] é false, [] → "" → 0, false → 0)
console.log([] == 0);         // true ([] → "" → 0)
console.log([1] == 1);        // true ([1] → "1" → 1)
console.log("1" == true);     // true ("1" → 1, true → 1)

Comparação de objetos sempre compara referência, não valor:

console.log({} == {});        // false (referências diferentes)
console.log([] == []);        // false
const a = [1]; const b = a;
console.log(a == b);          // true (mesma referência)

null e undefined são iguais com ==, mas diferentes com ===:

console.log(null == undefined);  // true
console.log(null === undefined); // false

5. Coerção em operadores lógicos e condicionais

Operadores como if, && e || usam o conceito de truthy e falsy. Valores falsy em JavaScript:

  • false
  • 0 (e -0)
  • "" (string vazia)
  • null
  • undefined
  • NaN

Todos os outros são truthy.

if ("") console.log("nunca executa");    // falsy
if ("texto") console.log("executa");     // truthy
if (0) console.log("nunca executa");     // falsy
if ([]) console.log("executa");          // array vazio é truthy!

Armadilha comum: confundir 0 com false ou "" com false:

const valor = 0;
if (valor == false) console.log("cuidado!"); // true com ==
if (valor === false) console.log("seguro");  // false com ===

6. Boas práticas para React e Node.js

No desenvolvimento com React e Node.js, o ESLint com a regra eqeqeq (ativada por padrão) proíbe o uso de ==, exceto em casos explícitos.

React — Comparação em hooks:

// Ruim: dependência pode causar re-renderizações inesperadas
useEffect(() => {
  fetchData();
}, [props.user == null]); // comportamento imprevisível

// Bom: use === ou expressões claras
useEffect(() => {
  fetchData();
}, [props.user === null]);

Node.js — Quando == null é seguro:

// Seguro: verifica null e undefined ao mesmo tempo
function processData(data) {
  if (data == null) {
    throw new Error('Dados inválidos');
  }
  // processa data...
}

// Equivalente com ===
if (data === null || data === undefined) {
  // ...
}

7. Coerção explícita: como controlar a conversão

Para evitar surpresas, converta tipos explicitamente:

// Para string
String(123);           // "123"
(123).toString();      // "123"
`${123}`;              // "123" (template literal)

// Para número
Number("42");          // 42
parseInt("42px", 10);  // 42
parseFloat("3.14");    // 3.14

// Para booleano
Boolean(1);            // true
!!"texto";             // true
!!0;                   // false

Diferença entre toString() e String():

const obj = { toString: () => "custom" };
console.log(String(obj));     // "custom"
console.log(obj.toString());  // "custom"

// Mas String() também funciona com null/undefined
console.log(String(null));    // "null"
// null.toString() lançaria TypeError

8. Testes e debugging de comparações

Use console.log e typeof para investigar coerção:

const valor = "5";
console.log(typeof valor);        // "string"
console.log(valor + 3);           // "53" (concatenação)
console.log(valor - 3);           // 2 (coerção numérica)
console.log(typeof (valor - 3));  // "number"

No Node.js REPL, teste interativamente:

$ node
> 0 == false
true
> [] == ![]
true
> typeof NaN
'number'

Em testes unitários (ex: Jest), seja explícito:

test('comparação segura', () => {
  expect(Number("42")).toBe(42);
  expect("42" === 42).toBe(false);
  expect(null == undefined).toBe(true); // se intencional
});

Referências