Executando verificação de segurança...
0

Coerção, Closures e Protótipos: As Mecânicas do JavaScript que Você Usa Sem Entender

Resumo rápido

Este post cobre três mecânicas internas do JavaScript que a maioria dos devs usa diariamente sem compreender o que acontece por baixo: coerção de tipos, closures com escopo léxico e cadeia de protótipos. Não é um tutorial introdutório. É uma análise de por que essas mecânicas se comportam como se comportam, com código funcional, anti-patterns reais e contexto de produção.

O bug de R$ 14 mil que começou com um ==

Em 2018, eu mantinha um sistema de conciliação financeira em Node.js (v10 na época) para um marketplace com ~8 mil transações/dia. Um sábado de madrugada, o job de conciliação marcou 247 transações como "valor zerado" e disparou estornos automáticos. O valor total dos estornos indevidos: R$ 14.312,00.

A causa raiz era uma comparação com == entre um valor vindo de uma API externa (string "0.00") e o número 0. Em JavaScript, "0.00" == 0 retorna true. O sistema interpretou que o valor da transação era zero e seguiu o fluxo de estorno. O fix foi trocar um único operador. O prejuízo levou semanas para reverter.

Esse tipo de bug nasce de uma lacuna específica: entender o que a coerção faz, mas não quando e por que o motor a dispara.

Coerção de tipos: as regras que a spec define (e que ninguém lê)

A coerção em JavaScript não é aleatória. O motor V8 (e SpiderMonkey, e JavaScriptCore) segue o algoritmo Abstract Equality Comparison definido na seção 7.2.14 da ECMAScript spec. O problema é que esse algoritmo tem 12 passos com ramificações, e a intuição humana falha em pelo menos metade deles.

Os três cenários que causam mais bugs

// Cenário 1: string numérica vs número
// A spec converte a string para número antes de comparar (passo 5 do algoritmo)
console.log("42" == 42);       // true — ToNumber("42") === 42
console.log("042" == 42);      // true — ToNumber ignora zero à esquerda em decimal
console.log("0x2A" == 42);     // true — ToNumber parseia hexadecimal

// Cenário 2: null e undefined são iguais APENAS entre si
// Passo 2 e 3 do algoritmo: retorna true se um é null e outro undefined
console.log(null == undefined); // true
console.log(null == 0);         // false — null NÃO é convertido para número aqui
console.log(null == "");        // false — mesmo motivo

// Cenário 3: objetos vs primitivos
// A spec chama ToPrimitive no objeto, que invoca valueOf() e depois toString()
const createdAt = new Date("2025-01-15");
console.log(createdAt == "Wed Jan 15 2025 00:00:00 GMT+0000"); // true — toString() do Date
console.log(createdAt == 1736899200000); // false — porque toString() foi chamado, não valueOf()

Esse último cenário é contraintuitivo. Quando um objeto é comparado com uma string via ==, o motor chama ToPrimitive com hint "default", que para Date resulta em toString(). Quando comparado com um número, o hint continua "default" (não "number"), e Date sobrescreve o comportamento padrão para preferir string. Outros objetos (como arrays) preferem valueOf().

A tabela que resolve 90% das dúvidas

ExpressãoResultadoPor quê
"" == falsetrueAmbos convertem para 0 via ToNumber
"0" == falsetrueToNumber("0") → 0, ToNumber(false) → 0
[] == falsetrueToPrimitive([]) → "", ToNumber("") → 0
[] == ![]true![] é false (objeto é truthy), depois [] == false
NaN == NaNfalsePasso 1(c) da spec: NaN não é igual a nada, nem a si mesmo
null == falsefalsenull só é == a undefined, nada mais
" \t\n" == 0trueToNumber ignora whitespace, string vazia vira 0

A solução não é "sempre use ==="

Eu uso === como padrão, sim. Mas o conselho cego de "nunca use ==" ignora que existem dois casos em que ==` é intencionalmente útil:

// Checar null OU undefined com uma única comparação
// Isso é idiomático e mais legível que (value === null || value === undefined)
function getConfig(value) {
  if (value == 

---

Leia o artigo completo em [https://vivodecodigo.com.br/backend/coercao-closures-prototipos-mecanicas-javascript-producao](https://vivodecodigo.com.br/backend/coercao-closures-prototipos-mecanicas-javascript-producao)
Carregando publicação patrocinada...
0