JavaScript é uma linguagem de tipagem dinâmica e fraca, o que significa que as variáveis podem conter valores de qualquer tipo e que a conversão entre tipos ocorre implicitamente em muitas operações. Nesta aula, vamos mergulhar nos tipos de dados que formam a base da linguagem: os primitivos e o tipo objeto. Entender esses tipos é fundamental para evitar bugs e escrever código previsível.

Além de conhecer cada tipo, veremos suas particularidades, exemplos de uso e as famosas "pegadinhas" que pegam até desenvolvedores experientes. Prepare-se para desvendar mistérios como typeof null, o comportamento de NaN e as regras da coerção de tipos.

Tipos primitivos e objeto

Em JavaScript, os tipos de dados são classificados em duas categorias: tipos primitivos e o tipo objeto. Os primitivos são imutáveis e não possuem métodos próprios (embora pareçam ter, por causa do autoboxing). São eles: string, number, bigint, boolean, undefined, symbol e null (este último é um primitivo, mas typeof retorna "object" — uma pegadinha histórica).

Já o tipo objeto é uma coleção de propriedades, mutável e com identidade. Inclui objetos simples, arrays, funções, datas, expressões regulares, etc. Praticamente tudo que não é primitivo é um objeto. A diferença fundamental é que objetos são armazenados por referência, enquanto primitivos são armazenados por valor.

// Primitivos: armazenados por valor
let a = 10;
let b = a;
b = 20;
console.log(a); // 10 (não foi alterado)

// Objetos: armazenados por referência
let obj1 = { valor: 10 };
let obj2 = obj1;
obj2.valor = 20;
console.log(obj1.valor); // 20 (foi alterado)

Particularidades de cada tipo

String: Sequência de caracteres Unicode. Imutável: qualquer operação que pareça modificar a string retorna uma nova string. Pode ser delimitada por aspas simples, duplas ou crases (template literals).

let nome = "João";
console.log(nome[0]); // "J"
nome[0] = "P"; // não altera, strings são imutáveis
console.log(nome); // "João"

Number: Representa números de ponto flutuante (IEEE 754). Inclui valores especiais: Infinity, -Infinity e NaN (Not-a-Number). Não há distinção entre inteiros e decimais.

console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log(0 / 0); // NaN
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991

BigInt: Para inteiros de precisão arbitrária. Criado adicionando n ao final do número. Não pode ser misturado com number em operações aritméticas.

const big = 123456789012345678901234567890n;
console.log(big + 1n); // 123456789012345678901234567891n

Boolean: Apenas true e false. Usado em condicionais. Qualquer valor pode ser convertido para booleano (truthy/falsy).

console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean("texto")); // true

Undefined: Valor atribuído a variáveis declaradas mas não inicializadas. Também é o retorno de funções sem return explícito.

let x;
console.log(x); // undefined

Null: Representa a ausência intencional de um valor. É um primitivo, mas typeof null retorna "object" devido a um bug histórico.

let y = null;
console.log(typeof y); // "object" (pegadinha!)

Symbol: Valor único e imutável, frequentemente usado como chave de propriedade de objeto para evitar colisões.

const sym1 = Symbol("descricao");
const sym2 = Symbol("descricao");
console.log(sym1 === sym2); // false

Objeto: Coleção de pares chave-valor. Pode conter funções (métodos). Arrays e funções são subtipos de objeto.

let pessoa = { nome: "Maria", idade: 30 };
console.log(pessoa.nome); // "Maria"

Exemplos

Vamos ver exemplos práticos que ilustram o comportamento de cada tipo em operações comuns.

// String e Number: concatenação vs soma
console.log("5" + 3); // "53" (coerção para string)
console.log("5" - 3); // 2 (coerção para number)

// Boolean em contexto numérico
console.log(true + 1); // 2 (true vira 1)
console.log(false + 1); // 1 (false vira 0)

// Undefined e null em operações
console.log(undefined + 1); // NaN
console.log(null + 1); // 1 (null vira 0)

// Comparações estritas vs soltas
console.log(0 == false); // true (coerção)
console.log(0 === false); // false (tipos diferentes)

Pegadinhas (typeof null, NaN, coerção)

typeof null: Como mencionado, typeof null retorna "object". Isso é um erro de implementação que não foi corrigido para não quebrar código legado. Para verificar se um valor é null, use valor === null.

NaN: NaN é o único valor em JavaScript que não é igual a si mesmo. NaN === NaN é false. Para verificar se um valor é NaN, use Number.isNaN() (ES6) ou isNaN() (que faz coerção).

console.log(NaN === NaN); // false
console.log(Number.isNaN(NaN)); // true
console.log(isNaN("texto")); // true (coerção: Number("texto") é NaN)
console.log(Number.isNaN("texto")); // false (não faz coerção)

Coerção de tipos: JavaScript converte automaticamente tipos em operações. Isso pode levar a resultados inesperados. Exemplos clássicos:

console.log([] + []); // "" (arrays viram strings vazias e concatenam)
console.log([] + {}); // "[object Object]"
console.log({} + []); // 0 (em alguns contextos, {} é interpretado como bloco vazio)
console.log(1 + "2" + "2"); // "122"
console.log(1 + +"2" + "2"); // "32" (o unário + converte "2" para number)
console.log("A" - "B" + "2"); // "NaN2" ("A"-"B" é NaN, depois concatena com "2")

Boas práticas

Para evitar surpresas, prefira sempre a comparação estrita (=== e !==) em vez da solta (== e !=). Use Number.isNaN() em vez de isNaN() para verificar NaN sem coerção. E lembre-se: null deve ser verificado com === null, não com typeof.

Referências

Exercícios

  1. Qual é o resultado de typeof null? Por que isso acontece?

    ✓ Resposta: typeof null retorna "object". Isso é um erro de implementação no JavaScript desde sua criação, que não foi corrigido para não quebrar código legado. Para verificar null, use === null.
  2. Explique por que NaN === NaN é false e como verificar se um valor é NaN corretamente.

    ✓ Resposta: NaN (Not-a-Number) é o único valor em JavaScript que não é igual a si mesmo, conforme especificação IEEE 754. Para verificar corretamente, use Number.isNaN(valor) (ES6) que não faz coerção, ou isNaN(valor) que faz coerção para number antes de verificar.
  3. Qual a diferença entre == e ===? Dê um exemplo onde o resultado difere.

    ✓ Resposta: == (igualdade solta) faz coerção de tipos antes da comparação; === (igualdade estrita) não faz coerção, comparando valor e tipo. Exemplo: 0 == false retorna true (pois false é convertido para 0), enquanto 0 === false retorna false (tipos diferentes).
  4. O que é autoboxing em JavaScript? Dê um exemplo.

    ✓ Resposta: Autoboxing é o processo automático pelo qual um primitivo é temporariamente convertido em seu objeto wrapper correspondente para acessar métodos. Exemplo: "texto".toUpperCase() — a string primitiva é convertida temporariamente em um objeto String para chamar o método toUpperCase().
  5. Qual o resultado de 1 + "2" + "2" e de 1 + +"2" + "2"? Explique.

    ✓ Resposta: 1 + "2" + "2" resulta em "122": o primeiro + é concatenação (número + string), gerando "12", depois concatena com "2". Já 1 + +"2" + "2" resulta em "32": o operador unário + converte "2" para número 2, então 1 + 2 = 3 (número), depois 3 + "2" vira "32" (concatenação).