Nesta aula, você aprenderá sobre os operadores em JavaScript, fundamentais para realizar operações em variáveis e valores. Entenderemos desde os operadores aritméticos básicos até os modernos operadores de coalescência nula (??), encadeamento opcional (?.) e spread/rest. Além disso, veremos como a precedência dos operadores afeta a execução das expressões.

Os operadores são símbolos que instruem o interpretador a realizar operações específicas, como matemática, comparação ou lógica. Dominá-los é essencial para escrever código eficiente e evitar bugs comuns.

Aritméticos

Os operadores aritméticos são usados para realizar operações matemáticas em números. JavaScript oferece os operadores tradicionais: adição (+), subtração (-), multiplicação (*), divisão (/) e módulo (%). Além disso, temos o operador de exponenciação (**) e os operadores de incremento (++) e decremento (--).

É importante notar que o operador + também é usado para concatenação de strings. Se um dos operandos for uma string, o JavaScript converte o outro operando em string e concatena. Para garantir a adição numérica, certifique-se de que ambos os operandos são números.

let a = 10;
let b = 3;

console.log(a + b); // 13
console.log(a - b); // 7
console.log(a * b); // 30
console.log(a / b); // 3.3333...
console.log(a % b); // 1
console.log(a ** b); // 1000

// Incremento e decremento
let x = 5;
console.log(x++); // 5 (pós-incremento: retorna o valor original)
console.log(x);   // 6
console.log(++x); // 7 (pré-incremento: retorna o valor incrementado)

// Cuidado com concatenação
console.log(10 + "5"); // "105"
console.log(10 - "5"); // 5 (subtração força conversão numérica)

Comparação (== vs ===)

Os operadores de comparação comparam dois valores e retornam um booleano (true ou false). Os principais são: igualdade (==), igualdade estrita (===), diferença (!=), diferença estrita (!==), maior (>), menor (<), maior ou igual (>=) e menor ou igual (<=).

A diferença entre == e === é crucial: == compara os valores após realizar coerção de tipo (conversão automática), enquanto === compara valor e tipo sem conversão. É recomendado usar === na maioria dos casos para evitar resultados inesperados.

console.log(5 == "5");  // true (coerção: string "5" vira número 5)
console.log(5 === "5"); // false (tipos diferentes)
console.log(0 == false); // true
console.log(0 === false); // false
console.log(null == undefined); // true
console.log(null === undefined); // false

// Outros exemplos
console.log(10 > 5);  // true
console.log(10 <= 10); // true
console.log("a" < "b"); // true (comparação lexicográfica)

Lógicos

Os operadores lógicos são usados para combinar expressões booleanas: AND (&&), OR (||) e NOT (!). Eles seguem a lógica de curto-circuito: no &&, se o primeiro operando for false, o segundo não é avaliado; no ||, se o primeiro for true, o segundo não é avaliado. Isso é útil para atribuições condicionais.

Além disso, os operadores lógicos retornam o valor de um dos operandos, não necessariamente um booleano. Isso permite usá-los em contextos de atribuição.

let idade = 20;
let temCarteira = true;

// AND
console.log(idade >= 18 && temCarteira); // true

// OR
console.log(idade >= 18 || temCarteira); // true

// NOT
console.log(!temCarteira); // false

// Curto-circuito e retorno de valores
console.log(0 && "texto"); // 0 (primeiro falso)
console.log("texto" || "padrão"); // "texto" (primeiro verdadeiro)
console.log(null || "padrão"); // "padrão"

Operadores modernos (??, ?., spread/rest)

O operador de coalescência nula (??) retorna o operando da direita se o da esquerda for null ou undefined, caso contrário retorna o da esquerda. Diferente do ||, ele não considera outros valores falsy (como 0, "", false) como nulos.

O encadeamento opcional (?.) permite acessar propriedades de objetos de forma segura, sem lançar erro se a referência for null ou undefined. Se a expressão antes do ?. for null/undefined, a avaliação para e retorna undefined.

Os operadores spread (...) e rest (...) têm a mesma sintaxe, mas usos diferentes: spread expande elementos de um array ou objeto em outro contexto; rest agrupa argumentos em um array em funções ou desestruturação.

// Coalescência nula
let valor = 0;
console.log(valor ?? 10); // 0 (0 não é null/undefined)
console.log(valor || 10); // 10 (0 é falsy)

// Encadeamento opcional
const usuario = { nome: "João", endereco: null };
console.log(usuario.endereco?.cidade); // undefined (sem erro)
// console.log(usuario.endereco.cidade); // TypeError!

// Spread em arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5];
const combinado = [...arr1, ...arr2]; // [1,2,3,4,5]

// Spread em objetos
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, ...obj1 }; // {c:3, a:1, b:2}

// Rest em parâmetros
function soma(...numeros) {
  return numeros.reduce((acc, n) => acc + n, 0);
}
console.log(soma(1, 2, 3, 4)); // 10

Precedência

A precedência de operadores determina a ordem em que as operações são avaliadas em uma expressão. Operadores com maior precedência são avaliados primeiro. A associatividade define a ordem quando operadores de mesma precedência aparecem (esquerda para direita ou direita para esquerda).

É recomendado usar parênteses para tornar a intenção clara, mesmo que você saiba a precedência. Por exemplo, a expressão 2 + 3 * 4 é avaliada como 2 + (3 * 4) = 14, pois * tem precedência maior que +.

// Precedência: multiplicação antes de adição
console.log(2 + 3 * 4); // 14

// Usando parênteses para alterar
console.log((2 + 3) * 4); // 20

// Operadores lógicos: ! > && > ||
console.log(true || false && false); // true (&& avaliado primeiro: false && false = false; depois true || false = true)
console.log((true || false) && false); // false

// Tabela resumida (do maior para menor precedência):
// 1. () (agrupamento)
// 2. !, ++, -- (unários)
// 3. *, /, %
// 4. +, -
// 5. <, <=, >, >=
// 6. ==, !=, ===, !==
// 7. &&
// 8. ||
// 9. ??
// 10. =, +=, etc. (atribuição)

Boas práticas e observações finais

Sempre prefira === e !== para comparações, a menos que você explicitamente queira a coerção de tipo. Use parênteses para tornar expressões complexas mais legíveis. O operador ?? é útil para valores default que consideram apenas null/undefined, enquanto || é mais abrangente (qualquer falsy). O encadeamento opcional é uma ferramenta poderosa para evitar erros ao acessar propriedades aninhadas de objetos que podem ser nulos.

Entender a precedência é crucial para evitar bugs sutis. Em caso de dúvida, use parênteses.

Referências

Exercícios

  1. Qual é o resultado da expressão 10 + "5" - 2? Explique.

    ✓ Resposta: O resultado é 103. Primeiro, a adição 10 + "5" resulta em "105" (concatenação). Depois, "105" - 2 converte a string para número (105) e subtrai 2, resultando em 103.
  2. Explique a diferença entre == e === com exemplos.

    ✓ Resposta: == compara valores após coerção de tipo, enquanto === compara valor e tipo sem coerção. Exemplo: 5 == "5" é true, mas 5 === "5" é false.
  3. O que o operador ?? retorna? Dê um exemplo onde ele se comporta diferente de ||.

    ✓ Resposta: ?? retorna o operando da direita se o da esquerda for null ou undefined. Exemplo: 0 ?? 10 retorna 0, enquanto 0 || 10 retorna 10 (pois 0 é falsy).
  4. Usando o encadeamento opcional, como acessar a propriedade cidade de um objeto usuario.endereco que pode ser null?

    ✓ Resposta: usuario.endereco?.cidade. Se endereco for null ou undefined, a expressão retorna undefined sem erro.
  5. Qual é a precedência dos operadores + e *? O que a expressão 2 + 3 * 4 retorna?

    ✓ Resposta: O operador * tem precedência maior que +. Portanto, 2 + 3 * 4 é avaliado como 2 + (3 * 4) = 2 + 12 = 14.