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 é únicotypeof 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) // trueTruthy, 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 objetosFunçõ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); // falseIIFE, 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 cacheadosObjetos
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 prototypePrototype 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 mutarMé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); // trueModules 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; // okPerformance 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