Frontend

JavaScript

Referência completa de JavaScript moderno — fundamentos, funções, objetos, arrays, ES6+, Promises, async/await, Event Loop (browser), DOM e Web APIs

Fundamentos

JavaScript tem três formas de declarar variáveis. A regra prática é: prefira const sempre, use let quando precisar reatribuir e nunca use var em código novo — var tem escopo de função e hoisting que geram bugs difíceis de rastrear.

// const — não pode ser reatribuída (o valor interno de objetos/arrays ainda muda)
const PI = 3.14159;
const usuario = { nome: "Rafael" };
usuario.nome = "Carlos"; // ok — a referência não muda, o objeto sim

// let — escopo de bloco, reatribuível
let contador = 0;
contador += 1;

// var — escopo de função, sofre hoisting (evite)
function exemploVar() {
  if (true) {
    var x = 10; // vazou para a função toda
  }
  console.log(x); // 10 — surpresa indesejada
}

Tipos Primitivos

// string — imutável, sequência de UTF-16
const nome = "Rafael";
const saudacao = `Olá, ${nome}!`; // template literal

// number — IEEE 754 double (64-bit), inteiros seguros até 2^53 - 1
const preco = 19.99;
const maximo = Number.MAX_SAFE_INTEGER; // 9007199254740991
console.log(0.1 + 0.2 === 0.3); // false — imprecisão de ponto flutuante

// bigint — inteiros arbitrariamente grandes (ES2020)
const idGrande = 9007199254740993n;
const soma = idGrande + 1n; // operações exigem ambos bigint

// boolean
const ativo = true;

// null — ausência intencional de valor
const sem = null;

// undefined — variável declarada mas não inicializada
let naoIniciado; // undefined

// symbol — identificador único e imutável
const id = Symbol("descricao");
const outroId = Symbol("descricao");
console.log(id === outroId); // false — cada Symbol é único

typeof e Coerção

// typeof — retorna string com o tipo
typeof "texto"    // "string"
typeof 42         // "number"
typeof true       // "boolean"
typeof undefined  // "undefined"
typeof null       // "object" — bug histórico do JS, null não é objeto
typeof {}         // "object"
typeof []         // "object" — use Array.isArray() para verificar arrays
typeof function(){} // "function"
typeof Symbol()   // "symbol"
typeof 42n        // "bigint"

// Coerção implícita — JS converte tipos automaticamente
"5" + 3          // "53" — number virou string (+ com string concatena)
"5" - 3          // 2   — string virou number (operadores aritméticos convertem)
true + 1         // 2   — true vira 1
false + 1        // 1   — false vira 0
null + 1         // 1   — null vira 0
undefined + 1    // NaN — undefined não converte para número

// Coerção explícita — sempre prefira isso
Number("42")       // 42
Number("")         // 0
Number(null)       // 0
Number(undefined)  // NaN
String(42)         // "42"
Boolean(0)         // false
Boolean("")        // false
Boolean(null)      // false
Boolean(undefined) // false
Boolean(NaN)       // false
parseInt("42px", 10) // 42 — para de parsear no char inválido
parseFloat("3.14abc") // 3.14

// Verificação segura de NaN
Number.isNaN(NaN)        // true  — não faz coerção (use este)
isNaN("texto")           // true  — coerce antes de checar (evite)
Number.isFinite(Infinity) // false
Number.isInteger(3.0)    // true

Truthy, Falsy e Operadores

// Falsy — os 8 valores que se comportam como false
false, 0, -0, 0n, "", '', ``, null, undefined, NaN

// Truthy — tudo que não é falsy (incluindo "0", [], {})
"0", [], {}, -1, Infinity

// == faz coerção de tipo — evite
0 == false  // true
"" == false // true
null == undefined // true

// === não faz coerção — use sempre
0 === false  // false
null === undefined // false

// Nullish coalescing — ?? retorna o lado direito só se esquerdo for null/undefined
const nome = entrada ?? "Anônimo"; // "" e 0 passam — diferente de ||

// Optional chaining — ?.  retorna undefined em vez de lançar erro
const cidade = usuario?.endereco?.cidade; // undefined se endereco não existir
const primeiroTag = tags?.[0];            // seguro em arrays
const resultado = callback?.();           // seguro em chamadas

// Negação dupla — !! converte qualquer valor para boolean
!!null      // false
!!"Rafael"  // true
!!0         // false

// Spread — espalha iteráveis/objetos
const nums = [1, 2, 3];
const copia = [...nums, 4, 5]; // [1, 2, 3, 4, 5]
const merged = { ...obj1, ...obj2 }; // combina objetos

Funções

Funções são cidadãos de primeira classe em JavaScript — podem ser passadas como argumento, retornadas por outras funções e atribuídas a variáveis. A escolha entre declaração, expressão e arrow function afeta principalmente o comportamento de this.

// Declaração — sofre hoisting, disponível antes da linha onde está escrita
function somar(a, b) {
  return a + b;
}

// Expressão — não sofre hoisting
const multiplicar = function(a, b) {
  return a * b;
};

// Arrow function — this léxico (herda o this do escopo externo)
const dividir = (a, b) => a / b;         // retorno implícito
const quadrado = n => n * n;             // parênteses opcionais com 1 parâmetro
const saudacao = () => "Olá, mundo!";   // sem parâmetros, precisa de ()

// Arrow functions NÃO têm próprio this, arguments, super ou new.target
// Use função regular quando precisar de this dinâmico (métodos de objeto, construtores)
const cronometro = {
  segundos: 0,
  iniciar() {
    // this aqui é cronometro — ok
    setInterval(() => {
      this.segundos += 1; // this ainda é cronometro — arrow herda
    }, 1000);
  }
};

Closures

// Closure — função que lembra o escopo onde foi criada
function criarContador(inicio = 0) {
  let count = inicio; // capturada pela closure

  return {
    incrementar: () => ++count,
    decrementar: () => --count,
    valor: () => count
  };
}

const c = criarContador(10);
c.incrementar(); // 11
c.incrementar(); // 12
c.decrementar(); // 11
c.valor();       // 11 — count não é acessível de fora

// Uso prático: fábrica de validadores
function criarValidadorIdade(minimo) {
  return (idade) => idade >= minimo; // minimo fica capturado
}

const maiorIdade = criarValidadorIdade(18);
const podeVotar = criarValidadorIdade(16);
maiorIdade(20); // true
podeVotar(15);  // false

IIFE, Ordem Superior, Currying e Memoization

// IIFE — executa imediatamente, evita poluir escopo global
const config = (() => {
  const chave = "valor-privado"; // não vaza
  return { chave, versao: "1.0" };
})();

// Funções de ordem superior — recebem ou retornam funções
function aplicarDuasVezes(fn, valor) {
  return fn(fn(valor));
}
aplicarDuasVezes(x => x * 2, 3); // 12

// Parâmetros default e rest
function criarURL(base, path = "/", ...queryParams) {
  const query = queryParams.join("&");
  return `${base}${path}${query ? "?" + query : ""}`;
}
criarURL("https://api.io", "/users", "page=1", "limit=10");
// "https://api.io/users?page=1&limit=10"

// Currying — transforma f(a,b,c) em f(a)(b)(c)
const curry = (fn) => {
  const aridade = fn.length;
  return function curried(...args) {
    if (args.length >= aridade) return fn(...args);
    return (...mais) => curried(...args, ...mais);
  };
};

const add = curry((a, b, c) => a + b + c);
add(1)(2)(3); // 6
add(1, 2)(3); // 6
const add10 = add(10); // função parcialmente aplicada

// Memoization — cacheia resultados de chamadas puras
function memoizar(fn) {
  const cache = new Map();
  return (...args) => {
    const chave = JSON.stringify(args);
    if (cache.has(chave)) return cache.get(chave);
    const resultado = fn(...args);
    cache.set(chave, resultado);
    return resultado;
  };
}

const fibonacci = memoizar(function fib(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});
fibonacci(40); // rápido — resultados intermediários cacheados

Objetos

Objetos são coleções de pares chave-valor onde os valores podem ser qualquer tipo, incluindo funções. Em JS, quase tudo é objeto ou se comporta como um (exceto null e undefined).

// Criação literal com property shorthand (ES6)
const nome = "Rafael";
const idade = 30;
const pessoa = { nome, idade }; // equivale a { nome: nome, idade: idade }

// Computed keys — chave dinâmica
const campo = "email";
const contato = {
  [campo]: "rafael@exemplo.com",       // chave vem de uma variável
  [`prefixo_${campo}`]: "pref_email"   // expressão como chave
};

// Getter e setter — propriedades computadas
const circulo = {
  _raio: 5,
  get raio() { return this._raio; },
  set raio(valor) {
    if (valor <= 0) throw new RangeError("Raio deve ser positivo");
    this._raio = valor;
  },
  get area() { return Math.PI * this._raio ** 2; } // somente leitura
};
circulo.raio = 10;
circulo.area; // 314.159...

// Métodos utilitários de Object
const src = { a: 1, b: 2 };
Object.keys(src)    // ["a", "b"]
Object.values(src)  // [1, 2]
Object.entries(src) // [["a", 1], ["b", 2]]

// Object.assign — cópia rasa (shallow)
const copia = Object.assign({}, src, { c: 3 });

// Spread de objetos — idiomático e legível (ES2018)
const copiaSrc = { ...src };
const extendido = { ...src, c: 3, b: 99 }; // b sobrescreve o original

// Object.freeze — congela o objeto (shallow — nested ainda é mutável)
const constantes = Object.freeze({ MAX: 100, MIN: 0 });
constantes.MAX = 999; // silencioso em modo não-strict, erro em strict

// Object.create — cria objeto com prototype explícito
const animal = {
  respirar() { return `${this.nome} respira`; }
};
const gato = Object.create(animal);
gato.nome = "Felix";
gato.respirar(); // "Felix respira" — método herdado do prototype

Prototype Chain e Destructuring

// Prototype chain — propriedades são buscadas subindo na cadeia
function Veiculo(tipo) { this.tipo = tipo; }
Veiculo.prototype.descrever = function() { return `Veículo: ${this.tipo}`; };

const carro = new Veiculo("carro");
carro.descrever(); // encontra em Veiculo.prototype
carro.hasOwnProperty("tipo");     // true  — própria do carro
carro.hasOwnProperty("descrever"); // false — está no prototype
"descrever" in carro;              // true  — busca na cadeia toda

// Destructuring de objetos
const { nome: nomeUsuario, idade = 18, endereco: { cidade } = {} } = usuario;
// nomeUsuario, idade (default 18 se undefined), cidade (nested + default)

// Rest em objetos — coleta o que sobrou
const { a, b, ...resto } = { a: 1, b: 2, c: 3, d: 4 };
// resto = { c: 3, d: 4 }

// Destructuring em parâmetros de função
function renderizar({ titulo, descricao = "", ativo = true }) {
  return `${titulo}: ${descricao} (${ativo ? "ativo" : "inativo"})`;
}

Arrays

Arrays são objetos especiais com índices numéricos e métodos poderosos. Os métodos funcionais (map, filter, reduce) retornam novos arrays sem mutar o original — fundamental para código previsível.

// Criação
const vazio = [];
const nums = [1, 2, 3, 4, 5];
const misto = [1, "dois", true, null, { x: 0 }];
const dezeros = Array(10).fill(0);
const indices = Array.from({ length: 5 }, (_, i) => i); // [0,1,2,3,4]
const deIteravel = Array.from("Rafael"); // ["R","a","f","a","e","l"]
const deSet = Array.from(new Set([1, 2, 2, 3])); // [1, 2, 3]

// Spread e destructuring
const [primeiro, segundo, ...outros] = [10, 20, 30, 40];
// primeiro=10, segundo=20, outros=[30,40]
const combinado = [...nums, 6, 7]; // [1,2,3,4,5,6,7]

// Mutação — modificam o array original
const lista = [3, 1, 4];
lista.push(5);             // adiciona no final → [3,1,4,5]
lista.pop();               // remove do final → [3,1,4]
lista.unshift(0);          // adiciona no início → [0,3,1,4]
lista.shift();             // remove do início → [3,1,4]
lista.splice(1, 1, 9, 8);  // (índice, quantos remover, ...inserir) → [3,9,8,4]
lista.sort((a, b) => a - b); // sort estável desde V8 7.0 / Node 11

// Não-mutantes — retornam novo array
nums.slice(1, 3)  // [2, 3] — do índice 1 até (excluindo) 3
nums.concat([6])  // [1,2,3,4,5,6]
nums.reverse()    // muta! use [...nums].reverse() para não mutar

Métodos Funcionais

const produtos = [
  { nome: "Notebook", preco: 3500, categoria: "tech" },
  { nome: "Mouse", preco: 120, categoria: "tech" },
  { nome: "Cadeira", preco: 800, categoria: "moveis" },
  { nome: "Mesa", preco: 600, categoria: "moveis" }
];

// map — transforma cada elemento (retorna novo array de mesmo tamanho)
const nomes = produtos.map(p => p.nome);
// ["Notebook", "Mouse", "Cadeira", "Mesa"]

// filter — mantém elementos que passam no predicado
const tech = produtos.filter(p => p.categoria === "tech");

// reduce — acumula em um único valor
const totalTech = tech.reduce((acc, p) => acc + p.preco, 0); // 3620

// find / findIndex — primeiro que satisfaz o predicado
const notebook = produtos.find(p => p.nome === "Notebook");
const idx = produtos.findIndex(p => p.preco > 1000); // 0

// some / every
produtos.some(p => p.preco > 3000);  // true — pelo menos um
produtos.every(p => p.preco > 0);    // true — todos

// flat / flatMap
const aninhado = [[1, 2], [3, [4, 5]]];
aninhado.flat();    // [1, 2, 3, [4, 5]] — profundidade 1
aninhado.flat(2);   // [1, 2, 3, 4, 5]
aninhado.flat(Infinity); // aplaina tudo

const frases = ["Olá mundo", "foo bar"];
frases.flatMap(f => f.split(" ")); // ["Olá","mundo","foo","bar"]
// flatMap = map seguido de flat(1) — mais eficiente que os dois separados

// Set operations — deduplica e opera com sets
const a = [1, 2, 3, 4];
const b = [3, 4, 5, 6];
const uniao = [...new Set([...a, ...b])];          // [1,2,3,4,5,6]
const intersecao = a.filter(v => b.includes(v));   // [3,4]
const diferenca = a.filter(v => !b.includes(v));   // [1,2]

ES6+ Modern Features

O JavaScript evoluiu muito desde 2015. Estas features modernas tornam o código mais expressivo, seguro e eficiente — conhecê-las bem é o que separa código legível de código “funcionou mas ninguém entende”.

// Template literals — interpolação e multilinha
const html = `
  <article>
    <h1>${titulo}</h1>
    <p>${descricao.trim()}</p>
  </article>
`.trim();

// Tagged templates — processamento customizado
function sql(strings, ...valores) {
  return { query: strings.join("?"), params: valores };
}
const q = sql`SELECT * FROM users WHERE id = ${userId} AND ativo = ${true}`;
// { query: "SELECT * FROM users WHERE id = ? AND ativo = ?", params: [userId, true] }

// Classes com ES2022+ features
class ContaBancaria {
  // Campos privados (ES2022) — não acessíveis fora da classe
  #saldo = 0;
  #historico = [];

  static #totalContas = 0;
  static get totalContas() { return ContaBancaria.#totalContas; }

  constructor(titular, saldoInicial = 0) {
    this.titular = titular;
    this.#saldo = saldoInicial;
    ContaBancaria.#totalContas++;
  }

  depositar(valor) {
    if (valor <= 0) throw new RangeError("Valor deve ser positivo");
    this.#saldo += valor;
    this.#historico.push({ tipo: "depósito", valor, data: new Date() });
    return this;  // permite encadeamento
  }

  get saldo() { return this.#saldo; }
}

class ContaCorrente extends ContaBancaria {
  #limite;

  constructor(titular, saldoInicial, limite = 1000) {
    super(titular, saldoInicial); // chama o constructor pai
    this.#limite = limite;
  }

  sacar(valor) {
    if (valor > this.saldo + this.#limite) throw new Error("Limite excedido");
    // ...
    return this;
  }
}

// Logical assignment (ES2021)
let config = {};
config.tema ??= "dark";     // atribui só se null/undefined
config.debug ||= false;     // atribui só se falsy
config.nivel &&= config.nivel.toUpperCase(); // atribui só se truthy

// Numeric separators (ES2021) — legibilidade sem afetar o valor
const populacaoBrasil = 215_000_000;
const bytesEmGB = 1_073_741_824;
const hex = 0xFF_AA_BB;

// structuredClone (ES2022) — deep clone nativo
const original = { a: 1, nested: { b: [2, 3] }, data: new Date() };
const clone = structuredClone(original);
clone.nested.b.push(4);
original.nested.b; // [2, 3] — não foi afetado

// Object.groupBy (ES2024)
const agrupado = Object.groupBy(produtos, p => p.categoria);
// { tech: [...], moveis: [...] }

// WeakMap — chaves são objetos, sem impedir garbage collection
const metadados = new WeakMap();
metadados.set(elemento, { criado: Date.now() }); // não vaza memória quando elemento for coletado

// WeakSet — similar, mas só armazena objetos
const visitados = new WeakSet();
visitados.add(no);
visitados.has(no); // true

Modules ESM

ESM (ECMAScript Modules) é o sistema de módulos nativo do JavaScript. Cada arquivo é um módulo com seu próprio escopo — nada vaza para o escopo global automaticamente.

// --- utils/math.js ---
// Named exports — múltiplos por arquivo
export const PI = 3.14159;
export function somar(a, b) { return a + b; }
export function subtrair(a, b) { return a - b; }

// Default export — um por arquivo, importado com qualquer nome
export default class Calculadora { /* ... */ }

// Re-export — expõe de outro módulo sem importar localmente
export { somar, subtrair } from "./operacoes.js";
export * from "./helpers.js";                      // re-exporta tudo
export * as helpers from "./helpers.js";           // como namespace

// --- main.js ---
import Calculadora from "./utils/math.js";              // default
import { somar, PI } from "./utils/math.js";            // named
import { somar as add } from "./utils/math.js";         // alias
import * as math from "./utils/math.js";                // namespace

// Dynamic import — lazy loading sob demanda
async function carregarGrafico() {
  const { renderizar } = await import("./grafico.js"); // chunk separado
  renderizar(dados);
}

// import.meta.url — URL do módulo atual
const __dirname = new URL(".", import.meta.url).pathname;

// Barrel file — index.js que reexporta o módulo
// src/components/index.js
export { default as Button } from "./Button.js";
export { default as Card } from "./Card.js";
export { default as Modal } from "./Modal.js";
// Consumidor importa de um lugar só
import { Button, Card } from "./components";

Iteradores e Generators

O protocolo de iteração define como qualquer objeto pode ser percorrido com for...of. Generators são funções que produzem valores sob demanda, pausando entre cada yield — úteis para sequências infinitas e processamento lazy.

// Symbol.iterator — torna qualquer objeto iterável
class Intervalo {
  constructor(inicio, fim) {
    this.inicio = inicio;
    this.fim = fim;
  }

  [Symbol.iterator]() {
    let atual = this.inicio;
    const fim = this.fim;
    return {
      next() {
        return atual <= fim
          ? { value: atual++, done: false }
          : { value: undefined, done: true };
      }
    };
  }
}

for (const n of new Intervalo(1, 5)) {
  console.log(n); // 1, 2, 3, 4, 5
}
const arr = [...new Intervalo(1, 3)]; // [1, 2, 3]

// Generator function — produz valores com yield
function* contar(inicio = 0, passo = 1) {
  let n = inicio;
  while (true) {        // sequência infinita — só avança quando pedido
    const reset = yield n;   // yield suspende e pode receber um valor
    n = reset ?? (n + passo);
  }
}

const gen = contar(0, 5);
gen.next();       // { value: 0, done: false }
gen.next();       // { value: 5, done: false }
gen.next(100);    // { value: 100, done: false } — reset recebeu 100

// yield* — delega para outro iterável
function* combinar(...iteraveis) {
  for (const iter of iteraveis) yield* iter;
}
[...combinar([1, 2], [3, 4], [5])]; // [1, 2, 3, 4, 5]

// Async generator + for await...of
async function* buscarPaginas(url) {
  let pagina = 1;
  while (true) {
    const resp = await fetch(`${url}?page=${pagina}`);
    const dados = await resp.json();
    if (dados.length === 0) return;
    yield dados;
    pagina++;
  }
}

async function processarTodos() {
  for await (const pagina of buscarPaginas("/api/itens")) {
    pagina.forEach(item => processar(item));
  }
}

Promises

Promises representam um valor que estará disponível no futuro. Elas resolvem o “callback hell” ao permitir encadeamento linear de operações assíncronas.

// Criação manual — quando encapsular código callback-based
function lerArquivo(caminho) {
  return new Promise((resolve, reject) => {
    fs.readFile(caminho, "utf8", (erro, dados) => {
      if (erro) reject(erro);  // rejeita com o erro
      else resolve(dados);     // resolve com os dados
    });
  });
}

// then / catch / finally — encadeamento
lerArquivo("config.json")
  .then(texto => JSON.parse(texto))    // cada then recebe o retorno do anterior
  .then(config => aplicar(config))
  .catch(erro => console.error(erro))  // captura qualquer erro na cadeia
  .finally(() => fecharLoader());      // sempre executa

// Combinadores — operações em múltiplas promises
const [usuarios, produtos] = await Promise.all([
  fetch("/api/usuarios").then(r => r.json()),
  fetch("/api/produtos").then(r => r.json())
]);
// Promise.all rejeita se QUALQUER promise rejeitar

const resultados = await Promise.allSettled([p1, p2, p3]);
// Nunca rejeita — cada item: { status: "fulfilled"|"rejected", value|reason }
resultados.forEach(r => {
  if (r.status === "fulfilled") processar(r.value);
  else console.error("Falhou:", r.reason);
});

// Promise.race — resolve/rejeita com a primeira que terminar
const timeout = new Promise((_, reject) =>
  setTimeout(() => reject(new Error("Timeout")), 5000)
);
const resultado = await Promise.race([buscarDados(), timeout]);

// Promise.any — resolve com a primeira que RESOLVER (ignora rejeições)
// Só rejeita se TODAS rejeitarem (AggregateError)
const dadosRapidos = await Promise.any([cdn1(), cdn2(), cdn3()]);

// Microtask queue — promises executam antes do próximo macrotask
Promise.resolve("micro").then(v => console.log(v));
setTimeout(() => console.log("macro"), 0);
// Ordem: "micro" antes de "macro"

async/await

async/await é açúcar sintático sobre Promises que torna código assíncrono legível como código síncrono. Toda função async retorna implicitamente uma Promise.

// Básico
async function buscarUsuario(id) {
  const resposta = await fetch(`/api/usuarios/${id}`);
  if (!resposta.ok) throw new Error(`HTTP ${resposta.status}`);
  return resposta.json(); // retorna a Promise implicitamente
}

// try/catch em async — trata tanto erros de rede quanto de aplicação
async function carregarPerfil(id) {
  try {
    const usuario = await buscarUsuario(id);
    const posts = await buscarPosts(usuario.id); // espera usuário primeiro
    return { usuario, posts };
  } catch (erro) {
    console.error("Falha ao carregar perfil:", erro);
    return null;
  } finally {
    esconderLoader();
  }
}

// Paralelo real com Promise.all — NÃO use await em sequência quando independentes
async function carregarDashboard(userId) {
  // Errado — sequencial desnecessário (soma dos tempos)
  const usuario = await buscarUsuario(userId);
  const stats = await buscarStats(userId);

  // Certo — paralelo (tempo do mais lento)
  const [usuario2, stats2] = await Promise.all([
    buscarUsuario(userId),
    buscarStats(userId)
  ]);
  return { usuario: usuario2, stats: stats2 };
}

// Catch no await — sem try/catch explícito
async function buscarSeguro(url) {
  const [erro, dados] = await fetch(url)
    .then(r => r.json())
    .then(d => [null, d])
    .catch(e => [e, null]);

  if (erro) return { erro };
  return { dados };
}

// AbortController — cancelamento de operações assíncronas
async function buscarComTimeout(url, timeoutMs = 5000) {
  const controller = new AbortController();
  const id = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const resp = await fetch(url, { signal: controller.signal });
    clearTimeout(id);
    return await resp.json();
  } catch (erro) {
    if (erro.name === "AbortError") throw new Error("Requisição cancelada por timeout");
    throw erro;
  }
}

// Top-level await (em módulos ESM)
const config = await fetch("/config.json").then(r => r.json());
export { config };

Event Loop (browser)

O JavaScript é single-threaded mas lida com concorrência através do Event Loop. Entender a ordem de execução evita bugs sutis de timing e garante interfaces responsivas.

// Call Stack — executa código síncrono em ordem LIFO
function a() { b(); }
function b() { c(); }
function c() { console.log("c"); }
a(); // call stack: a → b → c → (retorna) c → b → a

// Microtask queue — drena COMPLETAMENTE antes de cada macrotask
// Fontes: Promise.then/catch/finally, queueMicrotask(), MutationObserver

// Macrotask queue (task queue) — uma por iteração do loop
// Fontes: setTimeout, setInterval, setImmediate (Node), I/O, eventos UI

console.log("1 — síncrono");

setTimeout(() => console.log("4 — macrotask"), 0);

Promise.resolve()
  .then(() => console.log("2 — microtask"))
  .then(() => console.log("3 — microtask (encadeado)"));

console.log("1b — síncrono");
// Ordem: 1, 1b, 2, 3, 4

// queueMicrotask — enfileira microtask explicitamente
queueMicrotask(() => console.log("também é microtask"));

// Microtasks se encadeiam sem ceder ao loop:
Promise.resolve()
  .then(() => {
    queueMicrotask(() => console.log("microtask enfileirada dentro de microtask"));
    console.log("microtask 1");
  })
  .then(() => console.log("microtask 2"));
// Ordem: microtask 1, microtask enfileirada..., microtask 2

// requestAnimationFrame — antes da próxima pintura do browser
// Ideal para animações — sincroniza com o refresh do display (60fps = ~16ms)
function animar(timestamp) {
  // timestamp é DOMHighResTimeStamp
  elemento.style.transform = `translateX(${timestamp / 10 % 200}px)`;
  requestAnimationFrame(animar); // agenda próximo frame
}
requestAnimationFrame(animar);

// Rendering pipeline (em ordem por frame):
// 1. Macrotask (ex: evento de clique)
// 2. Microtasks (drena completamente)
// 3. requestAnimationFrame callbacks
// 4. Layout + Paint (se DOM mudou)
// 5. Próxima iteração do loop

// Evitar bloquear a thread principal
// Processar arrays grandes de forma cooperativa
async function processarEmLotes(items, tamLote = 100) {
  for (let i = 0; i < items.length; i += tamLote) {
    const lote = items.slice(i, i + tamLote);
    lote.forEach(processar);
    // Cede controle ao browser para renderizar/responder eventos
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

Tratamento de Erros

Erros bem tratados fazem a diferença entre um sistema confiável e um que falha silenciosamente. JavaScript tem um conjunto rico de tipos de erro nativos que comunicam a natureza do problema.

// Tipos nativos de Error
new TypeError("esperava string, recebi number");
new RangeError("índice 42 fora do intervalo [0, 10]");
new ReferenceError("variável não declarada");
new SyntaxError("JSON malformado"); // só lançado internamente pelo parser

// Custom errors — extends Error para erros de domínio
class ErroValidacao extends Error {
  constructor(campo, mensagem, valor) {
    super(mensagem);
    this.name = "ErroValidacao"; // sobrescreve "Error"
    this.campo = campo;
    this.valor = valor;
    // Garante stack trace correto no V8
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, ErroValidacao);
    }
  }
}

class ErroHttp extends Error {
  constructor(status, url) {
    super(`HTTP ${status} em ${url}`);
    this.name = "ErroHttp";
    this.status = status;
    this.url = url;
  }

  get recuperavel() { return this.status >= 500; }
}

// Error chaining com cause (ES2022)
async function buscarConfig(id) {
  try {
    return await fetch(`/config/${id}`).then(r => r.json());
  } catch (causa) {
    // Encadeia o erro original para preservar contexto
    throw new Error(`Falha ao buscar configuração ${id}`, { cause: causa });
  }
}

// try/catch/finally
try {
  const dados = JSON.parse(textoInvalido);
} catch (erro) {
  if (erro instanceof SyntaxError) {
    console.error("JSON inválido:", erro.message);
  } else {
    throw erro; // re-lança erros que não sabe tratar
  }
} finally {
  limpar(); // sempre executa — ideal para liberar recursos
}

// Promises não tratadas
window.addEventListener("unhandledrejection", (evento) => {
  console.error("Promise não tratada:", evento.promise, evento.reason);
  evento.preventDefault(); // impede log padrão do browser (opcional)
});

// Erros síncronos globais
window.addEventListener("error", (evento) => {
  console.error(`${evento.message} em ${evento.filename}:${evento.lineno}`);
});

DOM API

O DOM é a interface entre JavaScript e o documento HTML. Manipulação eficiente evita reflows desnecessários e mantém a UI responsiva.

// Seleção
const btn = document.querySelector("#enviar");
const cards = document.querySelectorAll(".card"); // NodeList (não é Array)
const cardArray = [...cards]; // converte para Array para usar métodos de array

// Criação e inserção
const el = document.createElement("div");
el.className = "alerta";
el.textContent = "Operação concluída"; // seguro — escapa HTML automaticamente

document.body.append(el);          // append aceita múltiplos nós e strings
document.body.prepend(el);         // início
btn.insertAdjacentHTML("beforebegin", `<label>Texto</label>`);
// "beforebegin" | "afterbegin" | "beforeend" | "afterend"

// innerHTML vs textContent
el.textContent = usuarioInput;  // seguro — não interpreta HTML
el.innerHTML = trusted;         // interpreta HTML — só para conteúdo confiável (risco XSS)

// dataset — atributos data-*
el.dataset.userId = "42";        // define data-user-id="42"
const id = el.dataset.userId;    // lê data-user-id
el.removeAttribute("data-user-id");

// classList
el.classList.add("ativo", "visivel");
el.classList.remove("oculto");
el.classList.toggle("expandido");           // adiciona ou remove
el.classList.toggle("expandido", condição); // força valor
el.classList.replace("antigo", "novo");
el.classList.contains("ativo"); // boolean

// Event listeners
function handleClick(evento) {
  evento.preventDefault();    // cancela comportamento padrão (ex: submit, link)
  evento.stopPropagation();   // impede bubbling para elementos pai
  console.log(evento.target); // elemento que disparou o evento
  console.log(evento.currentTarget); // elemento onde o listener foi registrado
}

btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick); // precisa da mesma referência de função

// Opções do addEventListener
btn.addEventListener("click", handler, {
  once: true,    // remove automaticamente após disparar uma vez
  passive: true, // nunca chama preventDefault (melhora performance de scroll)
  capture: true  // dispara na fase de captura (pai → filho) em vez de bubbling
});

// Event delegation — um listener para muitos filhos
document.querySelector("#lista").addEventListener("click", (e) => {
  const item = e.target.closest(".item"); // sobe na árvore até encontrar .item
  if (!item) return;
  processar(item.dataset.id);
});
// Vantagem: funciona para elementos adicionados dinamicamente, menos listeners

// Observers — reagem a mudanças sem polling
const mutObs = new MutationObserver((mutations) => {
  mutations.forEach(m => {
    m.addedNodes.forEach(no => console.log("Adicionado:", no));
  });
});
mutObs.observe(document.body, { childList: true, subtree: true, attributes: false });
mutObs.disconnect(); // para de observar

// IntersectionObserver — lazy loading e infinite scroll
const visObs = new IntersectionObserver(
  (entries) => entries.forEach(e => {
    if (e.isIntersecting) carregarImagem(e.target);
  }),
  { rootMargin: "200px", threshold: 0.1 }
);
document.querySelectorAll("img[data-src]").forEach(img => visObs.observe(img));

// ResizeObserver — reage ao redimensionamento de elementos
const resObs = new ResizeObserver(entries => {
  entries.forEach(({ target, contentRect }) => {
    console.log(`${target.id}: ${contentRect.width}x${contentRect.height}`);
  });
});
resObs.observe(document.querySelector(".container"));

Web APIs

APIs do browser modernizadas que tornam possível construir experiências ricas sem dependências externas. Fetch, Storage e Workers são os pilares do frontend atual.

// Fetch — requisições HTTP modernas
async function api(url, opcoes = {}) {
  const resp = await fetch(url, {
    method: opcoes.method ?? "GET",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${opcoes.token}`,
      ...opcoes.headers
    },
    body: opcoes.body ? JSON.stringify(opcoes.body) : undefined,
    signal: opcoes.signal
  });

  // fetch NÃO lança erro para status 4xx/5xx — você precisa verificar
  if (!resp.ok) {
    const texto = await resp.text();
    throw new ErroHttp(resp.status, url);
  }

  const ct = resp.headers.get("content-type");
  return ct?.includes("application/json") ? resp.json() : resp.text();
}

// AbortController no fetch — cancel/timeout
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 8000); // timeout de 8s
fetch("/api/lento", { signal: ctrl.signal })
  .catch(e => e.name === "AbortError" && console.log("Cancelado"));

// URL e URLSearchParams
const url = new URL("https://api.exemplo.com/busca");
url.searchParams.set("q", "javascript");
url.searchParams.set("page", 2);
url.searchParams.append("tag", "es6");
url.toString(); // "https://api.exemplo.com/busca?q=javascript&page=2&tag=es6"

const params = new URLSearchParams(window.location.search);
params.get("q");          // valor do parâmetro
params.has("page");       // boolean
[...params.entries()];    // array de [chave, valor]

// localStorage / sessionStorage
// localStorage: persiste entre sessões; sessionStorage: apenas na aba atual
localStorage.setItem("tema", "dark");
localStorage.getItem("tema");        // "dark"
localStorage.removeItem("tema");
localStorage.clear();                // remove tudo

// Wrapper com serialização JSON
const armazenamento = {
  set: (chave, valor) => localStorage.setItem(chave, JSON.stringify(valor)),
  get: (chave, padrao = null) => {
    try { return JSON.parse(localStorage.getItem(chave)) ?? padrao; }
    catch { return padrao; }
  },
  rem: (chave) => localStorage.removeItem(chave)
};

// Clipboard API
async function copiar(texto) {
  await navigator.clipboard.writeText(texto);
}
async function colar() {
  return navigator.clipboard.readText(); // exige permissão do usuário
}

// Custom Events — comunicação entre componentes sem acoplamento
const eventoBusca = new CustomEvent("busca:realizada", {
  detail: { termo: "javascript", resultados: 42 },
  bubbles: true,    // sobe pelo DOM
  cancelable: true
});
document.dispatchEvent(eventoBusca);
document.addEventListener("busca:realizada", e => console.log(e.detail));

// Web Workers — processamento pesado fora da thread principal
// worker.js
self.addEventListener("message", ({ data }) => {
  const resultado = processamentoPesado(data);
  self.postMessage(resultado);
});

// main.js
const worker = new Worker("worker.js");
worker.postMessage(dadosBrutos);
worker.addEventListener("message", ({ data }) => exibirResultado(data));
worker.terminate(); // encerra quando não precisar mais

// Broadcast Channel — comunicação entre abas/workers da mesma origem
const canal = new BroadcastChannel("atualizacoes");
canal.postMessage({ tipo: "usuario_atualizado", id: 42 });
canal.addEventListener("message", ({ data }) => sincronizar(data));
canal.close();

Proxy e Reflect

Proxy permite interceptar operações fundamentais em objetos — leitura, escrita, enumeração. Reflect espelha essas operações com a mesma API dos traps, facilitando o repasse da operação original.

// Proxy básico — intercepta leitura e escrita
const handler = {
  get(alvo, prop, receiver) {
    console.log(`Lendo: ${String(prop)}`);
    return Reflect.get(alvo, prop, receiver); // comportamento padrão
  },
  set(alvo, prop, valor, receiver) {
    if (typeof valor !== typeof alvo[prop] && alvo[prop] !== undefined) {
      throw new TypeError(`Tipo inválido para ${String(prop)}`);
    }
    console.log(`Escrevendo: ${String(prop)} = ${valor}`);
    return Reflect.set(alvo, prop, valor, receiver);
  },
  has(alvo, prop) {
    // intercepta operador 'in'
    return Reflect.has(alvo, prop);
  },
  deleteProperty(alvo, prop) {
    if (prop.startsWith("_")) throw new Error(`Não pode deletar ${String(prop)}`);
    return Reflect.deleteProperty(alvo, prop);
  }
};

const usuario = new Proxy({ nome: "Rafael", _senha: "hash" }, handler);
usuario.nome;        // "Lendo: nome" → "Rafael"
"nome" in usuario;   // interceptado pelo has trap
delete usuario._senha; // erro

// Caso de uso: reactive state simples (base dos frameworks)
function reativo(objeto, aoMudar) {
  return new Proxy(objeto, {
    set(alvo, prop, valor) {
      const anterior = alvo[prop];
      Reflect.set(alvo, prop, valor);
      if (anterior !== valor) aoMudar(prop, valor, anterior);
      return true;
    }
  });
}

const estado = reativo({ contador: 0 }, (prop, novo, velho) => {
  console.log(`${prop}: ${velho} → ${novo}`);
  atualizarUI();
});
estado.contador = 5; // "contador: 0 → 5" + atualiza UI

// Proxy para validação automática de schema
function validar(objeto, schema) {
  return new Proxy(objeto, {
    set(alvo, prop, valor) {
      if (schema[prop] && !schema[prop](valor)) {
        throw new TypeError(`Valor inválido para ${String(prop)}: ${valor}`);
      }
      return Reflect.set(alvo, prop, valor);
    }
  });
}

const produto = validar({}, {
  preco: v => typeof v === "number" && v > 0,
  nome: v => typeof v === "string" && v.length > 0
});
produto.preco = -10; // TypeError!
produto.preco = 100; // ok

Performance Patterns

Padrões de performance em JavaScript focam em não bloquear a thread principal, reduzir trabalho desnecessário e carregar código só quando precisar.

// Debounce — atrasa execução, reinicia o timer a cada chamada
// Ideal: input de busca, redimensionamento de janela
function debounce(fn, espera) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), espera);
  };
}

const busca = debounce(async (termo) => {
  const resultados = await api(`/busca?q=${termo}`);
  exibir(resultados);
}, 300);

inputBusca.addEventListener("input", e => busca(e.target.value));

// Throttle — garante que a função execute no máximo 1x por período
// Ideal: scroll, mousemove, eventos de alta frequência
function throttle(fn, limite) {
  let emEspera = false;
  return function(...args) {
    if (emEspera) return;
    fn.apply(this, args);
    emEspera = true;
    setTimeout(() => { emEspera = false; }, limite);
  };
}

window.addEventListener("scroll", throttle(() => {
  atualizarBarraProgresso();
}, 100));

// requestIdleCallback — executa quando o browser está ocioso
// Para tarefas não-urgentes que não afetam a experiência do usuário
function agendarTarefaOciosa(tarefas) {
  function processar(deadline) {
    while (deadline.timeRemaining() > 1 && tarefas.length > 0) {
      const tarefa = tarefas.shift();
      tarefa();
    }
    if (tarefas.length > 0) requestIdleCallback(processar);
  }
  requestIdleCallback(processar);
}

// Dynamic import — code splitting, carrega só o necessário
async function abrirEditor() {
  const { Editor } = await import("./editor-pesado.js");
  // Chunk separado, baixado só quando o usuário abrir o editor
  const editor = new Editor(container);
  editor.iniciar();
}

// WeakRef — referência fraca que não impede garbage collection
class Cache {
  #mapa = new Map();

  set(chave, valor) {
    this.#mapa.set(chave, new WeakRef(valor));
  }

  get(chave) {
    const ref = this.#mapa.get(chave);
    if (!ref) return undefined;
    const valor = ref.deref(); // retorna o valor ou undefined se foi coletado
    if (!valor) this.#mapa.delete(chave); // limpa entrada morta
    return valor;
  }
}

// FinalizationRegistry — callback quando objeto é coletado pelo GC
const registro = new FinalizationRegistry((chave) => {
  console.log(`Objeto com chave "${chave}" foi coletado`);
  limparRecursos(chave);
});

let obj = { dados: new ArrayBuffer(1024 * 1024) }; // 1MB
registro.register(obj, "chave-identificadora");
obj = null; // quando o GC coletar, o callback será chamado