DevTools

Cheatsheet JavaScript

Linguagem de programação para web e aplicações

Voltar às linguagens
JavaScript
93 cards encontrados
Categorias:
Versões:

Básico


13 cards
Declaração de Variáveis
let nome = "Ana";        // mutável, escopo de bloco
const PI = 3.14159;      // constante, não reatribuível
var antigo = "evitar";   // escopo de função (legacy)

// let pode ser reatribuído
nome = "Maria";

// const não pode ser reatribuído
// PI = 3;  // TypeError!

// const com objetos: a referência é fixa
const user = { nome: "João" };
user.nome = "Pedro";  // OK - muta o conteúdo
// user = {};         // TypeError!

Use const por padrão e let quando precisar reatribuir. Evite var — tem escopo de função e causa bugs com hoisting. const não impede mutação de objetos, apenas reatribuição da referência.

Truthy e Falsy
// Valores FALSY (6 no total):
false, 0, "", null, undefined, NaN

// Tudo o resto é TRUTHY:
true, 1, -1, "texto", [], {}, "0", "false"

// Exemplos práticos
if ("") { }       // não executa (falsy)
if ("texto") { }  // executa (truthy)
if ([]) { }       // executa! (array vazio é truthy)
if ({}) { }       // executa! (objeto vazio é truthy)

// Verificar booleano real
Boolean("");     // false
Boolean("oi");   // true
!!0;             // false (dupla negação)
!!"texto";       // true

// Uso comum: valor default
let nome = input || "Anónimo";

JavaScript tem 6 valores falsy: false, 0, "", null, undefined e NaN. Todo o resto é truthy, incluindo arrays e objetos vazios. Use !! para converter para booleano real.

Destructuring
// Arrays
const [a, b, c] = [1, 2, 3];
const [x, , z] = [1, 2, 3];  // pula índice 1
const [first, ...rest] = [1, 2, 3, 4];

// Default values
const [nome = "Anónimo"] = [];

// Objetos
const { name, age } = { name: "Ana", age: 30 };
const { name: n, age: a2 } = { name: "Ana", age: 30 };
// renomeia: n = "Ana", a2 = 30

// Nested
const { addr: { city } } = { addr: { city: "Porto" } };

// Em parâmetros de função
function mostrar({ nome, idade }) {
  console.log(`${nome}, ${idade}`);
}
mostrar({ nome: "Ana", idade: 30 });

// Trocar variáveis
let x = 1, y = 2;
[x, y] = [y, x];  // x=2, y=1

Destructuring extrai valores de arrays ou propriedades de objetos em variáveis. Suporta defaults, renomeação ({ name: n }), nesting e rest. Muito usado em parâmetros de funções.

typeof e instanceof
// typeof: retorna string com o tipo
typeof "texto";     // "string"
typeof 42;          // "number"
typeof true;        // "boolean"
typeof undefined;   // "undefined"
typeof {};          // "object"
typeof [];          // "object" (não distingue!)
typeof null;        // "object" (bug!)
typeof function(){};// "function"
typeof Symbol();    // "symbol"

// instanceof: verifica protótipo
[] instanceof Array;   // true
[] instanceof Object;  // true
new Date() instanceof Date;  // true

// Verificar array corretamente
Array.isArray([1,2]);  // true
Array.isArray({});     // false

// Verificar null
const isNull = val === null;
const isNil = val == null;  // null OU undefined

typeof retorna o tipo como string mas falha com arrays e null. Use Array.isArray() para arrays e === null para null. instanceof verifica a cadeia de protótipos.

Tipos Primitivos
// 7 tipos primitivos
let str = "texto";          // string
let num = 42;               // number
let big = 9007199254740991n; // bigint
let bool = true;            // boolean
let undef = undefined;      // undefined
let nulo = null;            // null (objeto)
let sym = Symbol("id");     // symbol

// Verificar tipo
typeof str;    // "string"
typeof num;    // "number"
typeof bool;   // "boolean"
typeof undef;  // "undefined"
typeof nulo;   // "object" (bug histórico!)
typeof sym;    // "symbol"
typeof big;    // "bigint"

JavaScript tem 7 tipos primitivos. O typeof null retorna "object" — é um bug histórico da linguagem. Use === null para verificar null explicitamente.

Conversão de Tipos
// String → Number
Number("42");       // 42
parseInt("42px");   // 42
parseFloat("3.14"); // 3.14
+"42";              // 42 (unário)

// Number → String
String(42);         // "42"
(42).toString();    // "42"
`${42}`;            // "42"

// → Boolean
Boolean(1);         // true
Boolean("");        // false
!!0;                // false

// Coerção implícita (evitar!)
"5" + 3;    // "53" (concatena!)
"5" - 3;    // 2    (converte!)
true + 1;   // 2
[] + {};    // "[object Object]"

// Verificar NaN
isNaN(NaN);         // true
Number.isNaN(NaN);  // true (mais seguro)

Use Number(), parseInt() ou o unário + para converter strings em números. Cuidado com coerção implícita: "5" + 3 concatena ("53") mas "5" - 3 converte (2). Prefira Number.isNaN() em vez de isNaN().

console.log Essencial
// Básico
console.log("Valor:", 42);
console.warn("Aviso!");
console.error("Erro!");
console.info("Info");

// Formatação CSS
console.log("%c Texto azul", "color: blue");

// Tabela (arrays/objetos)
console.table([{ a: 1, b: 2 }, { a: 3, b: 4 }]);

// Objeto com profundidade
console.dir(obj, { depth: null });

// Agrupar
console.group("Grupo");
console.log("dentro");
console.groupEnd();

// Assert
console.assert(1 === 2, "1 não é 2!");

Além de console.log(), use console.table() para arrays/objetos, console.dir() para inspecionar estrutura e console.group() para organizar logs relacionados.

Operadores de Comparação
// == vs === (SEMPRE use ===)
5 == "5";      // true  (coerção de tipo!)
5 === "5";     // false (sem coerção)
null == undefined;  // true
null === undefined; // false

// Operadores relacionais
10 > 5;       // true
10 <= 10;     // true
"a" < "b";    // true (ordem alfabética)

// Operadores lógicos
true && false;  // false (AND)
true || false;  // true  (OR)
!true;          // false (NOT)

// Nullish coalescing (??)
let val = null ?? "default";  // "default"
let zero = 0 ?? "default";    // 0 (não é null!)

Sempre use === e !== em vez de == e != para evitar coerção implícita. O operador ?? (nullish) só ativa com null ou undefined, ao contrário de || que também ativa com 0 e "".

Optional Chaining (?.)
const user = {
  nome: "Ana",
  endereco: {
    rua: "Av. Central",
    cidade: "Lisboa"
  }
};

// Sem optional chaining (verbose)
const cidade = user && user.endereco && user.endereco.cidade;

// Com optional chaining
const cidade2 = user?.endereco?.cidade;  // "Lisboa"
const pais = user?.endereco?.pais;       // undefined (sem erro!)

// Com métodos
const upper = user?.getNome?.();  // undefined se não existir

// Com arrays
const arr = [1, 2, 3];
arr?.[0];    // 1
arr?.[10];   // undefined

// Combinar com nullish
const nome = user?.nome ?? "Anónimo";

O ?. (optional chaining) acede a propriedades sem erro se o objeto for null/undefined. Retorna undefined em vez de lançar TypeError. Combine com ?? para valores default.

console — Performance e Contagem
// Medir tempo
console.time("operação");
// ... código ...
console.timeEnd("operação");  // "operação: 2.5ms"

// Contador
console.count("click");  // "click: 1"
console.count("click");  // "click: 2"
console.countReset("click");

// Trace (call stack)
function a() { b(); }
function b() { console.trace(); }
a();  // mostra a pilha de chamadas

// debugger: pausa no DevTools
function calcular(x) {
  debugger;  // breakpoint manual
  return x * 2;
}

console.time()/timeEnd() mede performance. console.count() conta chamadas. console.trace() mostra a pilha de chamadas. debugger; cria um breakpoint no código.

Template Literals
const nome = "Ana";
const idade = 30;

// Interpolação com crase
const msg = `Olá, ${nome}! Tens ${idade} anos.`;

// Expressões dentro
const total = `Total: ${2 + 3 * 4}€`;  // "Total: 14€"

// Multi-linha
const html = `
  <div>
    <h1>${nome}</h1>
    <p>Idade: ${idade}</p>
  </div>
`;

// Função dentro
const upper = `Nome: ${nome.toUpperCase()}`;

// Ternário dentro
const status = `${idade >= 18 ? "Adulto" : "Menor"}`;

Template literals usam crase ` e permitem interpolação com ${expressão}. Suportam multi-linha sem \n e aceitam qualquer expressão JavaScript dentro de ${}.

Spread e Rest (...)
// SPREAD: expandir elementos
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];  // [1,2,3,4,5]

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

// Copiar (shallow copy)
const copia = [...arr1];
const objCopia = { ...obj1 };

// REST: recolher argumentos
function soma(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}
soma(1, 2, 3, 4);  // 10

// Destructuring com rest
const [first, ...resto] = [1, 2, 3, 4];
// first = 1, resto = [2,3,4]

const { a, ...rest } = { a: 1, b: 2, c: 3 };
// a = 1, rest = {b:2, c:3}

O operador ... tem dois usos: spread expande arrays/objetos e rest recolhe argumentos restantes. Cria cópias rasas (shallow) — objetos aninhados partilham referência.

Operador Ternário
// Sintaxe: condição ? valorSeTrue : valorSeFalse
const idade = 20;
const status = idade >= 18 ? "Adulto" : "Menor";

// Encadeado (evitar mais de 2 níveis)
const nota = 15;
const resultado = nota >= 18 ? "Excelente"
  : nota >= 14 ? "Bom"
  : nota >= 10 ? "Suficiente"
  : "Insuficiente";

// Em JSX/templates
// {isAdmin ? <AdminPanel /> : <UserPanel />}

// Com nullish para defaults
const nome = user?.nome ?? "Anónimo";

// Atribuição condicional
let msg;
if (isAdmin) msg = "Bem-vindo admin";
else msg = "Bem-vindo";
// Equivalente:
const msg2 = isAdmin ? "Bem-vindo admin" : "Bem-vindo";

O operador ternário condição ? a : b é uma expressão condicional compacta. Ideal para atribuições simples. Evite encadear mais de 2 níveis — use if/else para lógica complexa.

Strings


6 cards
Busca e Verificação
const str = "Olá Mundo JavaScript";

str.length;              // 20
str.indexOf("Mundo");    // 4
str.lastIndexOf("a");    // 17
str.includes("Mundo");   // true
str.startsWith("Olá");   // true
str.endsWith("Script");  // true
str.search(/mundo/i);    // 4 (regex)

// match (retorna matches)
"2024-01-15".match(/\d+/g);  // ["2024","01","15"]

// at() - índice negativo
"JavaScript".at(-1);   // "t"
"JavaScript".at(-6);   // "S"

indexOf() retorna a posição (ou -1). includes(), startsWith() e endsWith() retornam boolean. at(-1) acede ao último caractere sem calcular o índice.

Unicode e Caracteres
// Código Unicode
"A".charCodeAt(0);         // 65
String.fromCharCode(65);   // "A"

// Code points (emoji)
"😀".codePointAt(0);       // 128512
String.fromCodePoint(128512);  // "😀"

// Iterar com for...of (respeita Unicode)
for (const char of "Olá 😀") {
  console.log(char);
}

// Normalização
"café".normalize("NFC");

// Escape
"linha1\nlinha2";   // nova linha
"tab\taqui";        // tab
"aspas \"aqui\"";   // aspas

// String.raw (sem escape)
String.raw`C:\novo\pasta`;  // "C:\novo\pasta"

// Comparação locale
"a".localeCompare("b");  // -1
"ã".localeCompare("a");  // 1

Use for...of para iterar strings com emoji (respeita surrogate pairs). String.raw ignora sequências de escape. localeCompare() compara strings com suporte a acentos.

Transformação de Strings
const str = "Olá Mundo JavaScript";

str.toUpperCase();       // "OLÁ MUNDO JAVASCRIPT"
str.toLowerCase();       // "olá mundo javascript"
str.slice(4, 9);         // "Mundo"
str.substring(4, 9);     // "Mundo"
str.replace("Mundo", "JS");  // "Olá JS JavaScript"
str.replaceAll("a", "o");    // "Olá Mundo JovaScript"
str.trim();              // remove espaços nas pontas
str.repeat(2);           // repete a string
str.charAt(0);           // "O"

// concat
"Olá".concat(" ", "Mundo");  // "Olá Mundo"

Strings são imutáveis — os métodos retornam novas strings. slice() aceita índices negativos, replace() só substitui a primeira ocorrência (use replaceAll() para todas).

localeCompare e Normalização
// Comparação com locale
"a".localeCompare("b");      // -1
"ã".localeCompare("a", "pt"); // 1 (depois)
"Água".localeCompare("banana", "pt"); // -1

// Ordenar com acentos
["ação", "Água", "banana"].sort(
  (a, b) => a.localeCompare(b, "pt")
);
// ["Água", "ação", "banana"]

// Normalização Unicode
"café".normalize("NFC");   // forma composta
"café".normalize("NFD");   // forma decomposta

// Verificar igualdade visual
const s1 = "\u00E9";       // é (composto)
const s2 = "e\u0301";      // é (decomposto)
s1 === s2;                  // false!
s1.normalize() === s2.normalize();  // true

localeCompare() compara strings respeitando o idioma e acentos. normalize() uniformiza representações Unicode — essencial para comparar strings com acentos de fontes diferentes.

split, padStart e search
// split: string → array
"a,b,c".split(",");       // ["a","b","c"]
"olá mundo".split(" ");   // ["olá","mundo"]
"abc".split("");          // ["a","b","c"]

// join: array → string
["a","b","c"].join("-");  // "a-b-c"

// padStart / padEnd
"5".padStart(3, "0");     // "005"
"42".padEnd(5, ".");      // "42..."

// search (com regex)
"Olá Mundo".search(/mundo/i);  // 4

// match (retorna matches)
"2024-01-15".match(/\d+/g);  // ["2024","01","15"]

// at() - índice negativo
"JavaScript".at(-1);   // "t"
"JavaScript".at(-6);   // "S"

// concat
"Olá".concat(" ", "Mundo");  // "Olá Mundo"

split() divide uma string em array por delimitador. padStart()/padEnd() preenchem até um tamanho. at(-1) acede ao último caractere sem calcular o índice.

Regex Básico
// Criação
const re1 = /padrão/gi;
const re2 = new RegExp("padrão", "gi");

// Flags: g (global), i (case-insensitive), m (multiline)

// Testar
/^\d+$/.test("12345");    // true
/^\d+$/.test("12a45");    // false

// Extrair
const email = "ana@mail.com";
email.match(/(\w+)@(\w+)/);
// [0]="ana@mail", [1]="ana", [2]="mail"

// Substituir
"2024-01-15".replace(/-/g, "/");  // "2024/01/15"

// Metacaracteres comuns
// \d = dígito, \w = palavra, \s = espaço
// . = qualquer, ^ = início, $ = fim
// + = 1+, * = 0+, ? = 0 ou 1
// {n,m} = entre n e m

// Exemplo: validar email simples
const emailRe = /^[\w.]+@[\w]+\.[\w]{2,}$/;
emailRe.test("ana@mail.com");  // true

Regex em JS usa /padrão/flags. .test() verifica se há match, .match() extrai grupos e .replace() substitui. Metacaracteres: \d (dígito), \w (palavra), ^/$ (início/fim).

Números e Math


5 cards
Arredondamento e Potência
// Arredondamento
Math.round(4.5);    // 5
Math.floor(4.9);    // 4 (para baixo)
Math.ceil(4.1);     // 5 (para cima)
Math.trunc(4.9);    // 4 (corta decimal)

// Mínimo e máximo
Math.max(1, 5, 3);  // 5
Math.min(1, 5, 3);  // 1
Math.max(...[1,5,3]);  // 5 (spread)

// Potência e raiz
Math.pow(2, 10);    // 1024
2 ** 10;            // 1024 (operador)
Math.sqrt(16);      // 4
Math.cbrt(27);      // 3

// Valor absoluto
Math.abs(-5);       // 5

Math.floor() arredonda para baixo, Math.ceil() para cima, Math.round() para o mais próximo. Use ** em vez de Math.pow() para potência. Math.max(...arr) com spread para arrays.

Sistemas Numéricos
// Binário (0b)
0b1010;        // 10
0b11111111;    // 255

// Octal (0o)
0o17;          // 15
0o777;         // 511

// Hexadecimal (0x)
0xFF;          // 255
0x1A;          // 26

// Separador visual (ES2021)
1_000_000;     // 1000000
0xFF_FF;       // 65535

// Converter bases
(255).toString(16);   // "ff"
(255).toString(2);    // "11111111"
(10).toString(8);     // "12"

// Parse de outras bases
parseInt("ff", 16);   // 255
parseInt("1010", 2);  // 10

Prefixos: 0b binário, 0o octal, 0x hexadecimal. toString(base) converte para string noutra base. parseInt(str, base) parseia de outra base. Underscores (1_000) melhoram legibilidade.

Aleatório e Constantes
// Aleatório
Math.random();              // 0 a 0.999...
Math.floor(Math.random() * 10);  // 0 a 9

// Inteiro entre min e max (inclusivo)
function aleatorio(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
aleatorio(1, 6);  // 1 a 6 (dado)

// Constantes
Math.PI;    // 3.14159...
Math.E;     // 2.71828...
Math.SQRT2; // 1.41421...

// Arredondar com decimais
function roundDec(num, casas) {
  const f = 10 ** casas;
  return Math.round(num * f) / f;
}
roundDec(3.14159, 2);  // 3.14

Math.random() retorna um float entre 0 e 1. Math.floor(Math.random() * n) gera inteiros de 0 a n-1. Math.PI e Math.E são constantes. Para casas decimais, multiplique, arredonde e divida.

Formatação de Números
const num = 1234.5678;

// Casas decimais
num.toFixed(2);       // "1234.57" (string!)
num.toPrecision(4);   // "1235"

// Locale (moeda, separadores)
num.toLocaleString("pt-PT");
// "1 234,568"

// Moeda
num.toLocaleString("pt-PT", {
  style: "currency",
  currency: "EUR"
});  // "1 234,57 €"

// Percentagem
(0.75).toLocaleString("pt-PT", {
  style: "percent"
});  // "75%"

// Notação científica
(1234567).toExponential(2);  // "1.23e+6"

// parseInt / parseFloat
parseInt("42px");     // 42
parseFloat("3.14em"); // 3.14
parseInt("0xFF", 16); // 255 (hex)

toFixed() retorna string — use Number() ou + para converter de volta. toLocaleString() formata com separadores e moeda do locale. parseInt() aceita base como 2º argumento.

Number: Métodos Estáticos
// Verificação
Number.isInteger(42);      // true
Number.isInteger(4.5);     // false
Number.isFinite(Infinity); // false
Number.isNaN(NaN);         // true
Number.isNaN("42");        // false (sem coerção!)

// Limites
Number.MAX_SAFE_INTEGER;   // 9007199254740991
Number.MIN_SAFE_INTEGER;   // -9007199254740991
Number.MAX_VALUE;          // 1.7976931348623157e+308
Number.EPSILON;            // 2.220446049250313e-16

// Comparação de floats
0.1 + 0.2 === 0.3;  // false!
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON;  // true

// BigInt (números enormes)
const big = 9007199254740993n;
big + 1n;  // 9007199254740994n
// big + 1;  // TypeError! (misturar tipos)

Number.isNaN() não faz coerção (ao contrário de isNaN() global). Floats têm imprecisão: 0.1 + 0.2 !== 0.3. Use BigInt (sufixo n) para inteiros acima de MAX_SAFE_INTEGER.

Arrays


8 cards
Criar e Aceder
// Criação
const arr = [1, 2, 3];
const arr2 = new Array(5);      // [empty x 5]
const arr3 = Array.of(1, 2, 3);
const arr4 = Array.from("abc"); // ["a","b","c"]
const arr5 = Array.from({length: 5}, (_, i) => i);
// [0, 1, 2, 3, 4]

// Acesso
arr[0];          // 1
arr.at(-1);      // 3 (último!)
arr.length;      // 3

// Verificar
arr.includes(2);       // true
arr.indexOf(2);        // 1
arr.findIndex(x => x > 1);  // 1

// Preencher
const filled = new Array(3).fill(0);  // [0,0,0]

// Sequência
[...Array(5).keys()];  // [0,1,2,3,4]

Array.from() converte iteráveis em arrays. at(-1) acede ao último elemento sem arr[arr.length-1]. Array.from({length: n}, fn) cria arrays com valores gerados.

find, some e every
const users = [
  { nome: "Ana", idade: 25 },
  { nome: "Pedro", idade: 30 },
  { nome: "Maria", idade: 35 },
];

// find: primeiro que satisfaz
users.find(u => u.idade > 28);
// { nome: "Pedro", idade: 30 }

// findIndex: índice do primeiro
users.findIndex(u => u.nome === "Maria");  // 2

// some: ALGUM satisfaz?
users.some(u => u.idade > 30);   // true

// every: TODOS satisfazem?
users.every(u => u.idade > 20);  // true
users.every(u => u.idade > 30);  // false

// includes (valor primitivo)
[1, 2, 3].includes(2);  // true

// findLast (ES2023)
users.findLast(u => u.idade > 28);
// { nome: "Maria", idade: 35 }

find() retorna o primeiro elemento que satisfaz a condição. some() verifica se algum passa, every() se todos passam. findLast() procura do fim para o início.

Adicionar e Remover
const arr = [1, 2, 3];

// Adicionar
arr.push(4);       // [1,2,3,4] (fim)
arr.unshift(0);    // [0,1,2,3,4] (início)

// Remover
arr.pop();         // remove último → 4
arr.shift();       // remove primeiro → 0

// splice: remover/inserir em posição
arr.splice(1, 2);       // remove 2 desde índice 1
arr.splice(1, 0, 9, 8); // insere 9,8 no índice 1

// slice: copiar parte (não muta)
arr.slice(1, 3);   // [2, 3]
arr.slice(-2);     // últimos 2

// concat (não muta)
[1,2].concat([3,4]);  // [1,2,3,4]
[...[1,2], ...[3,4]]; // [1,2,3,4] (spread)

// flat: achatar
[1, [2, [3]]].flat();    // [1, 2, [3]]
[1, [2, [3]]].flat(2);   // [1, 2, 3]
[1, [2, [3]]].flat(Infinity);  // [1, 2, 3]

push/pop operam no fim, unshift/shift no início. splice() muta o array original; slice() retorna cópia sem mutar. flat(Infinity) achata completamente.

Ordenação
// sort() MUTA o array original!
const nums = [10, 5, 40, 25];

// Numérico crescente
[...nums].sort((a, b) => a - b);
// [5, 10, 25, 40]

// Numérico decrescente
[...nums].sort((a, b) => b - a);
// [40, 25, 10, 5]

// Strings (alfabético)
["banana","apple","cherry"].sort();
// ["apple", "banana", "cherry"]

// Objetos por propriedade
const users = [{n:"Ana",i:25},{n:"Pedro",i:20}];
users.sort((a, b) => a.i - b.i);

// toSorted (ES2023 - não muta!)
const sorted = nums.toSorted((a, b) => a - b);
// nums permanece [10, 5, 40, 25]

// Locale compare (acentos)
["ação","banana","Água"].sort(
  (a, b) => a.localeCompare(b, "pt")
);

sort() muta o array original — use [...arr].sort() ou toSorted() (ES2023) para não mutar. Sem comparador, ordena como strings ("40" < "5"). Use localeCompare() para acentos.

map e filter
const nums = [1, 2, 3, 4, 5];

// map: transforma cada elemento
const dobro = nums.map(n => n * 2);
// [2, 4, 6, 8, 10]

// filter: mantém os que passam no teste
const pares = nums.filter(n => n % 2 === 0);
// [2, 4]

// map com índice
["a","b","c"].map((val, i) => `${i}:${val}`);
// ["0:a", "1:b", "2:c"]

// Encadear
const resultado = nums
  .filter(n => n > 2)
  .map(n => n * 10);
// [30, 40, 50]

map() transforma cada elemento e retorna novo array. filter() seleciona os que passam no teste. Ambos não mutam o original. Encadeie para pipelines de transformação.

forEach, for...of e entries
const arr = ["a", "b", "c"];

// forEach (não retorna nada)
arr.forEach((val, i) => {
  console.log(i, val);
});

// for...of (permite break/continue)
for (const val of arr) {
  if (val === "b") break;
  console.log(val);
}

// entries (índice + valor)
for (const [i, val] of arr.entries()) {
  console.log(i, val);
}

// keys / values
[...arr.keys()];    // [0, 1, 2]
[...arr.values()];  // ["a", "b", "c"]

// for tradicional (performance)
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

// flatMap (map + flat(1))
[[1,2],[3,4]].flatMap(a => a.map(x => x * 2));
// [2, 4, 6, 8]

forEach() não permite break — use for...of quando precisar parar a iteração. entries() dá índice + valor. flatMap() é map() + flat(1) combinados.

reduce — Acumulador
const nums = [1, 2, 3, 4, 5];

// Soma
const soma = nums.reduce((acc, n) => acc + n, 0);
// 15

// Reduce para objeto (agrupar)
const grouped = [6.1, 4.2, 6.3].reduce((acc, n) => {
  const key = Math.floor(n);
  acc[key] = (acc[key] || 0) + 1;
  return acc;
}, {});  // {4: 1, 6: 2}

// Pipeline completo
const total = nums
  .filter(n => n > 2)
  .map(n => n * 10)
  .reduce((acc, n) => acc + n, 0);
// 120

// flatMap (map + flat(1))
[[1,2],[3,4]].flatMap(a => a.map(x => x * 2));
// [2, 4, 6, 8]

reduce() acumula todos os elementos num único valor. O 2º argumento é o valor inicial do acumulador. Use para somar, agrupar ou transformar em objeto. flatMap() é map() + flat(1) combinados.

Métodos ES2023 (Não-Mutáveis)
const arr = [3, 1, 4, 1, 5];

// toSorted (não muta!)
const ordenado = arr.toSorted((a, b) => a - b);
// [1, 1, 3, 4, 5] | arr inalterado

// toReversed (não muta!)
const invertido = arr.toReversed();
// [5, 1, 4, 1, 3]

// toSpliced (não muta!)
const sem2 = arr.toSpliced(1, 2);
// [3, 1, 5] | remove 2 desde índice 1

// with (substituir por índice)
const trocado = arr.with(0, 99);
// [99, 1, 4, 1, 5]

// findLast / findLastIndex
arr.findLast(n => n < 4);       // 1
arr.findLastIndex(n => n < 4);  // 3

ES2023: toSorted(), toReversed(), toSpliced() e with() retornam novos arrays sem mutar o original. findLast() procura do fim para o início.

Objetos


6 cards
Criar e Manipular Objetos
// Literal
const obj = { nome: "Ana", idade: 30 };

// Acesso
obj.nome;           // "Ana"
obj["idade"];       // 30

// Adicionar/modificar
obj.email = "ana@mail.com";
obj.idade = 31;

// Remover
delete obj.email;

// Verificar
"nome" in obj;              // true
obj.hasOwnProperty("nome"); // true

// Chaves, valores, entradas
Object.keys(obj);    // ["nome", "idade"]
Object.values(obj);  // ["Ana", 31]
Object.entries(obj); // [["nome","Ana"],["idade",31]]

// Iterar
for (const [key, val] of Object.entries(obj)) {
  console.log(key, val);
}

Objetos são coleções de pares chave-valor. Object.keys(), Object.values() e Object.entries() retornam arrays iteráveis. delete remove uma propriedade.

JSON
const obj = { nome: "Ana", idade: 30, ativo: true };

// Objeto → JSON string
const json = JSON.stringify(obj);
// '{"nome":"Ana","idade":30,"ativo":true}'

// Formatado (pretty print)
JSON.stringify(obj, null, 2);

// Com filtro de chaves
JSON.stringify(obj, ["nome", "idade"]);

// JSON string → Objeto
const parsed = JSON.parse(json);
parsed.nome;  // "Ana"

// Com reviver (transformar valores)
JSON.parse(json, (key, val) => {
  return key === "idade" ? val + 1 : val;
});

// Cuidado: não suporta
// undefined, functions, Symbol, Date (vira string)
JSON.stringify(new Date());  // "2024-01-15T..."

JSON.stringify() converte objeto em string JSON. JSON.parse() faz o inverso. Não suporta undefined, funções ou Symbol. Datas viram strings ISO.

Object.assign e Freeze
// Merge (shallow)
const a = { x: 1 };
const b = { y: 2 };
const c = Object.assign({}, a, b);  // {x:1, y:2}
const d = { ...a, ...b };           // idem (spread)

// Clonar (shallow)
const clone = { ...a };
const clone2 = Object.assign({}, a);

// Congelar (imutável)
const config = Object.freeze({ port: 3000 });
config.port = 8080;  // ignorado (strict: erro)
config.port;         // 3000

// Verificar
Object.isFrozen(config);  // true

// Seal (não adiciona/remove, mas edita)
const sealed = Object.seal({ a: 1 });
sealed.a = 2;    // OK
sealed.b = 3;    // ignorado

// Comparar objetos
Object.is(NaN, NaN);  // true (=== dá false!)

Object.assign() e spread fazem merge shallow. Object.freeze() torna imutável (não adiciona, remove ou edita). Object.is() é como === mas trata NaN corretamente.

Computed Properties e fromEntries
// Computed property names
const campo = "nome";
const obj = { [campo]: "Ana", [campo + "2"]: "Maria" };
// { nome: "Ana", nome2: "Maria" }

// Chave dinâmica em método
const metodo = "saudar";
const pessoa = {
  [metodo]() { return "Olá!"; }
};
pessoa.saudar();  // "Olá!"

// Object.fromEntries (inverso de entries)
const pares = [["nome", "Ana"], ["idade", 30]];
Object.fromEntries(pares);
// { nome: "Ana", idade: 30 }

// Transformar valores de objeto
const precos = { a: 10, b: 20, c: 30 };
const comIva = Object.fromEntries(
  Object.entries(precos).map(([k, v]) => [k, v * 1.23])
);
// { a: 12.3, b: 24.6, c: 36.9 }

Computed properties ([expressão]) permitem chaves dinâmicas. Object.fromEntries() é o inverso de Object.entries() — converte pares em objeto. Útil para transformar valores de objetos.

Getters e Setters
const pessoa = {
  _nome: "Ana",
  _idade: 30,

  get nome() {
    return this._nome.toUpperCase();
  },

  set nome(val) {
    if (val.length < 2) throw new Error("Nome curto");
    this._nome = val;
  },

  get info() {
    return `${this._nome}, ${this._idade} anos`;
  }
};

pessoa.nome;       // "ANA" (getter)
pessoa.nome = "Maria";  // setter
pessoa.info;       // "Maria, 30 anos"

// Object.defineProperty
Object.defineProperty(pessoa, "id", {
  value: 123,
  writable: false,
  enumerable: false
});

Getters (get) e setters (set) permitem controlar acesso a propriedades. São acedidos como propriedades normais (sem parênteses). Object.defineProperty() define atributos como writable e enumerable.

Map e Set
// Map: objeto com chaves de qualquer tipo
const map = new Map();
map.set("nome", "Ana");
map.set(42, "número");
map.set({id: 1}, "objeto");

map.get("nome");    // "Ana"
map.has(42);        // true
map.size;           // 3
map.delete(42);

// Iterar Map
for (const [key, val] of map) {
  console.log(key, val);
}

// Set: coleção de valores únicos
const set = new Set([1, 2, 2, 3, 3]);
set.size;           // 3 (duplicados removidos!)
set.add(4);
set.has(2);         // true
set.delete(1);

// Remover duplicados de array
const unique = [...new Set([1,1,2,3,3])];
// [1, 2, 3]

Map aceita chaves de qualquer tipo (objetos, números) e mantém ordem de inserção. Set guarda valores únicos. Use [...new Set(arr)] para remover duplicados de um array.

Funções


6 cards
Arrow Functions
// Tradicional
function soma(a, b) {
  return a + b;
}

// Arrow function
const soma2 = (a, b) => a + b;

// Um parâmetro (parênteses opcionais)
const dobro = n => n * 2;

// Corpo com múltiplas linhas
const calcular = (a, b) => {
  const resultado = a * b;
  return resultado + 1;
};

// Sem parâmetros
const ola = () => "Olá!";

// Retorno de objeto (parênteses!)
const criar = (nome) => ({ nome, ativo: true });

// Diferença: this
// Arrow NÃO tem this próprio (herda do escopo)
const obj = {
  nome: "Ana",
  // ERRADO com arrow:
  // falar: () => console.log(this.nome),
  // CERTO com função normal:
  falar() { console.log(this.nome); }
};

Arrow functions (=>) têm sintaxe compacta e não têm this próprio — herdam do escopo envolvente. Para retornar objeto literal, envolva em parênteses: () => ({...}).

Métodos de Função
function saudar(saudacao, nome) {
  return `${saudacao}, ${nome}!`;
}

// call: invoca com this e args separados
saudar.call(null, "Olá", "Ana");
// "Olá, Ana!"

// apply: invoca com this e array de args
saudar.apply(null, ["Olá", "Ana"]);
// "Olá, Ana!"

// bind: cria nova função com this fixo
const olaAna = saudar.bind(null, "Olá", "Ana");
olaAna();  // "Olá, Ana!"

// Uso prático: fixar this
const btn = document.querySelector("button");
btn.addEventListener("click",
  this.handleClick.bind(this)
);

// Partial application
const multiply = (a, b) => a * b;
const double = multiply.bind(null, 2);
double(5);  // 10

call() e apply() invocam com this definido (diferem nos args: separados vs array). bind() cria uma nova função com this fixo — útil em event handlers.

Parâmetros Default e Rest
// Default parameters
function saudar(nome = "Mundo", pontuacao = "!") {
  return `Olá, ${nome}${pontuacao}`;
}
saudar();          // "Olá, Mundo!"
saudar("Ana");     // "Olá, Ana!"
saudar("Ana", "."); // "Olá, Ana."

// Rest parameters
function soma(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}
soma(1, 2, 3);  // 6

// arguments (legacy - evitar)
function legacy() {
  console.log(arguments.length);
}

// Named parameters (via destructuring)
function criarUser({ nome, idade = 18, ativo = true }) {
  return { nome, idade, ativo };
}
criarUser({ nome: "Ana", idade: 25 });

Parâmetros default (= valor) ativam quando o argumento é undefined. Rest (...args) recolhe argumentos num array. Named parameters via destructuring são ideais para funções com muitas opções.

Recursão e IIFE
// Recursão: função que se chama a si mesma
function fatorial(n) {
  if (n <= 1) return 1;  // caso base
  return n * fatorial(n - 1);
}
fatorial(5);  // 120

// Recursão com acumulador (tail)
function soma(n, acc = 0) {
  if (n === 0) return acc;
  return soma(n - 1, acc + n);
}
soma(100);  // 5050

// IIFE: Immediately Invoked Function Expression
(function() {
  const privado = "não acessível fora";
  console.log(privado);
})();

// IIFE com arrow (moderno)
(() => {
  const config = { debug: true };
  // setup inicial...
})();

Recursão: função que se chama a si mesma — precisa sempre de um caso base. IIFE executa imediatamente e cria escopo privado. Útil para setup inicial e evitar poluição do escopo global.

Closures
// Closure: função que "lembra" do escopo onde foi criada
function contador() {
  let count = 0;
  return {
    incrementar: () => ++count,
    decrementar: () => --count,
    valor: () => count
  };
}

const c = contador();
c.incrementar();  // 1
c.incrementar();  // 2
c.valor();        // 2
// count não é acessível diretamente!

// Uso prático: função factory
function multiplicador(fator) {
  return (n) => n * fator;
}
const dobro = multiplicador(2);
const triplo = multiplicador(3);
dobro(5);   // 10
triplo(5);  // 15

// Closure em loops (clássico)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 0, 1, 2 (let cria escopo por iteração)

Uma closure é uma função que mantém acesso ao escopo onde foi criada, mesmo após esse escopo terminar. Permite dados privados e factories. Use let em loops para criar escopo por iteração.

Callbacks e Higher-Order
// Callback: função passada como argumento
function processar(arr, callback) {
  return arr.map(callback);
}
processar([1,2,3], n => n * 2);  // [2,4,6]

// Higher-order: recebe/retorna funções
function aplicar(fn, valor) {
  return fn(valor);
}
aplicar(Math.sqrt, 16);  // 4

// Composição
const pipe = (...fns) => (x) =>
  fns.reduce((acc, fn) => fn(acc), x);

const transformar = pipe(
  x => x * 2,
  x => x + 1,
  x => `Resultado: ${x}`
);
transformar(5);  // "Resultado: 11"

// Debounce (callback adiado)
function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

Uma callback é uma função passada como argumento. Higher-order functions recebem ou retornam funções. pipe() compõe funções em sequência. debounce() atrasa execução até parar de chamar.

Controlo de Fluxo


4 cards
if/else e switch
// if/else
if (idade >= 18) {
  console.log("Adulto");
} else if (idade >= 12) {
  console.log("Adolescente");
} else {
  console.log("Criança");
}

// switch
switch (dia) {
  case 1:
  case 2:
  case 3:
  case 4:
  case 5:
    console.log("Útil");
    break;
  case 6:
  case 7:
    console.log("Fim de semana");
    break;
  default:
    console.log("Inválido");
}

// switch com expressões
switch (true) {
  case nota >= 18: console.log("Excelente"); break;
  case nota >= 14: console.log("Bom"); break;
  default: console.log("Suficiente");
}

if/else para condições simples. switch para múltiplos valores — não esqueça o break! switch(true) permite comparar expressões como um if/else encadeado.

for, for...of e for...in
// for tradicional
for (let i = 0; i < 5; i++) {
  console.log(i);
}

// for...of (arrays, strings, Map, Set)
for (const item of [1, 2, 3]) {
  console.log(item);
}

// for...in (chaves de objetos)
for (const key in { a: 1, b: 2 }) {
  console.log(key);  // "a", "b"
}

// for...of com índice
for (const [i, val] of ["a","b","c"].entries()) {
  console.log(i, val);
}

for...of itera valores (arrays, strings, Map, Set). for...in itera chaves (objetos). Prefira for...of com .entries() para ter índice + valor em arrays.

while, break e continue
// while
let n = 0;
while (n < 5) {
  n++;
}

// do...while (executa pelo menos 1x)
do {
  n--;
} while (n > 0);

// break e continue
for (let i = 0; i < 10; i++) {
  if (i === 3) continue;  // pula 3
  if (i === 7) break;     // para no 7
  console.log(i);  // 0,1,2,4,5,6
}

// Label (loops aninhados)
outer: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (i === 1 && j === 1) break outer;
  }
}

while repete enquanto a condição for verdadeira. do...while executa pelo menos uma vez. break sai do loop, continue pula para a próxima iteração. Labels permitem sair de loops aninhados.

Operadores Lógicos Avançados
// Short-circuit evaluation
const nome = input || "Anónimo";  // OR: 1º truthy
const config = user && user.config;  // AND: 1º falsy ou último

// Nullish coalescing (??)
const val = 0 ?? "default";   // 0 (não é null!)
const val2 = null ?? "default";  // "default"

// Logical assignment (ES2021)
let a = null;
a ??= "default";  // a = "default"

let b = 0;
b ||= 10;  // b = 10 (0 é falsy)
b ??= 20;  // b = 10 (0 não é null)

let c = { x: 1 };
c.x &&= 2;  // c.x = 2 (x existe e é truthy)

// Encadeamento de comparações
// ERRADO: 1 < x < 10
// CERTO:
const inRange = x > 1 && x < 10;

|| retorna o primeiro truthy, && o primeiro falsy ou o último. ?? só ativa com null/undefined. Operadores de atribuição lógica: ??=, ||=, &&=.

Datas e Tempo


4 cards
Objeto Date
// Criar datas
const agora = new Date();
const data = new Date(2024, 0, 15);  // 15 Jan 2024
const data2 = new Date("2024-01-15");
const data3 = new Date(2024, 0, 15, 14, 30, 0);

// Getters
agora.getFullYear();   // 2024
agora.getMonth();      // 0-11 (0 = Janeiro!)
agora.getDate();       // 1-31
agora.getDay();        // 0-6 (0 = Domingo)
agora.getHours();      // 0-23
agora.getMinutes();    // 0-59
agora.getTime();       // ms desde 1970

// Setters
data.setFullYear(2025);
data.setMonth(11);  // Dezembro

// Timestamp
Date.now();  // ms atuais

// Comparar
const d1 = new Date("2024-01-01");
const d2 = new Date("2024-12-31");
d1 < d2;  // true

getMonth() retorna 0-11 (0 = Janeiro!). Date.now() dá o timestamp em ms. Para comparar datas, use os operadores < e > diretamente.

Formatação de Datas
const data = new Date(2024, 0, 15, 14, 30);

// toLocaleDateString
data.toLocaleDateString("pt-PT");
// "15/01/2024"

data.toLocaleDateString("pt-PT", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});
// "segunda-feira, 15 de janeiro de 2024"

// toLocaleTimeString
data.toLocaleTimeString("pt-PT");
// "14:30:00"

// ISO
data.toISOString();
// "2024-01-15T14:30:00.000Z"

// Diferença entre datas (em dias)
const d1 = new Date("2024-01-01");
const d2 = new Date("2024-01-15");
const diffDias = (d2 - d1) / (1000 * 60 * 60 * 24);
// 14

toLocaleDateString() formata com locale. Subtração de datas dá milissegundos — divida por 1000*60*60*24 para dias. toISOString() retorna formato ISO 8601.

setTimeout e setInterval
// setTimeout: executa 1x após delay
const timer = setTimeout(() => {
  console.log("Passou 2s");
}, 2000);

// Cancelar
clearTimeout(timer);

// setInterval: repete a cada X ms
const interval = setInterval(() => {
  console.log("Tick");
}, 1000);

// Parar
clearInterval(interval);

// Contagem decrescente
let seg = 5;
const countdown = setInterval(() => {
  console.log(seg--);
  if (seg < 0) clearInterval(countdown);
}, 1000);

// requestAnimationFrame (60fps)
function animar() {
  // ... desenhar ...
  requestAnimationFrame(animar);
}
requestAnimationFrame(animar);

setTimeout() executa uma vez após delay. setInterval() repete. Ambos retornam um ID para cancelar com clearTimeout()/clearInterval(). Use requestAnimationFrame() para animações.

Intl.DateTimeFormat e Relative Time
// Intl.DateTimeFormat (reutilizável)
const fmt = new Intl.DateTimeFormat("pt-PT", {
  weekday: "long",
  day: "numeric",
  month: "long",
  year: "numeric"
});
fmt.format(new Date());
// "sexta-feira, 15 de março de 2024"

// RelativeTimeFormat ("há 3 dias")
const rtf = new Intl.RelativeTimeFormat("pt", {
  numeric: "auto"
});
rtf.format(-3, "day");    // "há 3 dias"
rtf.format(1, "month");   // "próximo mês"
rtf.format(-2, "hour");   // "há 2 horas"

// Diferença relativa
function tempoRelativo(data) {
  const diff = data - Date.now();
  const dias = Math.round(diff / 86400000);
  return rtf.format(dias, "day");
}

Intl.DateTimeFormat é reutilizável e mais rápido que toLocaleDateString() em loops. Intl.RelativeTimeFormat cria textos como "há 3 dias" ou "próximo mês". Suporta todos os locales.

DOM e Eventos


9 cards
Selecionar Elementos
// Um elemento
const el = document.getElementById("app");
const el2 = document.querySelector(".card");
const el3 = document.querySelector("#app > p");

// Vários elementos
const todos = document.querySelectorAll(".item");
const porClasse = document.getElementsByClassName("item");
const porTag = document.getElementsByTagName("p");

// querySelectorAll retorna NodeList (estático)
// getElementsBy* retorna HTMLCollection (vivo)
todos.forEach(el => console.log(el));

querySelector() aceita qualquer seletor CSS e retorna o primeiro. querySelectorAll() retorna todos como NodeList (tem forEach). getElementsBy* retorna HTMLCollection (atualiza em tempo real).

Delegação de Eventos
// Em vez de um listener por botão...
const lista = document.querySelector("#lista");

// Um listener no pai (delegação)
lista.addEventListener("click", (e) => {
  const item = e.target.closest("li");
  if (!item) return;
  
  console.log("Clicou em:", item.textContent);
  
  // Exemplo: remover item
  if (e.target.matches(".remover")) {
    item.remove();
  }
});

// Funciona para elementos futuros (dinâmicos)
const novoLi = document.createElement("li");
novoLi.innerHTML = 'Novo <button class="remover">X</button>';
lista.appendChild(novoLi);  // já tem listener!

Delegação: um listener no pai captura eventos dos filhos via bubbling. Use e.target.closest() para encontrar o elemento pretendido. Funciona para elementos adicionados dinamicamente sem registar novos listeners.

MutationObserver
// Observar mudanças no DOM
const observer = new MutationObserver((mutations) => {
  mutations.forEach(m => {
    if (m.type === "childList") {
      console.log("Filhos alterados:", m.addedNodes.length);
    }
    if (m.type === "attributes") {
      console.log(`Atributo ${m.attributeName} mudou`);
    }
  });
});

// Configurar o que observar
observer.observe(document.body, {
  childList: true,      // adição/remoção de filhos
  attributes: true,     // mudanças de atributos
  subtree: true,        // incluir descendentes
  characterData: true   // mudanças de texto
});

// Parar de observar
observer.disconnect();

MutationObserver deteta mudanças no DOM (filhos, atributos, texto). Substitui o antigo MutationEvent (mais performante). Útil para editores WYSIWYG, auto-save e deteção de mudanças.

Criar e Inserir Elementos
// Criar
const div = document.createElement("div");
div.textContent = "Olá";
div.classList.add("card");
div.id = "novo";

// Inserir
const pai = document.querySelector("#app");
pai.appendChild(div);          // último filho
pai.prepend(div);              // primeiro filho
pai.append(div, "texto");      // múltiplos

// Inserir relativo a outro elemento
const ref = document.querySelector(".ref");
ref.before(div);   // antes de ref
ref.after(div);    // depois de ref
ref.replaceWith(div);  // substitui ref

// Remover
div.remove();

createElement() cria o elemento, appendChild()/prepend() inserem no pai. before()/after() inserem relativo a um elemento. remove() elimina do DOM.

innerHTML vs textContent
const div = document.querySelector("#app");

// textContent: só texto (seguro)
div.textContent = "<b>Negrito</b>";
// Mostra: <b>Negrito</b> (literal)

// innerHTML: parseia HTML (cuidado XSS!)
div.innerHTML = "<b>Negrito</b>";
// Mostra: Negrito (formatado)

// insertAdjacentHTML (não limpa filhos)
div.insertAdjacentHTML("beforeend", "<p>Fim</p>");
div.insertAdjacentHTML("afterbegin", "<p>Inicio</p>");

// Performance: DocumentFragment
const frag = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement("li");
  li.textContent = i;
  frag.appendChild(li);
}
div.appendChild(frag);  // 1 reflow

textContent é seguro (não parseia HTML). innerHTML parseia mas é vulnerável a XSS. insertAdjacentHTML() insere sem limpar. DocumentFragment evita múltiplos reflows.

Manipular Classes e Estilos
const el = document.querySelector(".card");

// classList
el.classList.add("ativo", "visivel");
el.classList.remove("oculto");
el.classList.toggle("dark");
el.classList.contains("ativo");  // true
el.classList.replace("old", "new");

// Estilos inline
el.style.color = "red";
el.style.backgroundColor = "#333";
el.style.cssText = "color: red; font-size: 16px;";

// Ler estilos computados
const estilos = getComputedStyle(el);
estilos.fontSize;  // "16px"

// Atributos
el.setAttribute("data-id", "42");
el.getAttribute("data-id");  // "42"
el.dataset.id;  // "42" (data-*)

classList gere classes sem sobrescrever. toggle() adiciona/remove. style define estilos inline. getComputedStyle() lê o CSS final. dataset acede a atributos data-*.

Formulários e Inputs
const form = document.querySelector("form");
const input = document.querySelector("#nome");

// Ler valores
input.value;           // texto do input
input.checked;         // checkbox/radio
input.files;           // file input

// FormData (todos os campos)
const dados = new FormData(form);
dados.get("nome");
dados.append("extra", "valor");

// Submit com fetch
form.addEventListener("submit", async (e) => {
  e.preventDefault();
  const dados = new FormData(form);
  const res = await fetch("/api", {
    method: "POST",
    body: dados
  });
});

// Validação nativa
input.validity.valid;       // true/false
input.checkValidity();      // dispara invalid
input.setCustomValidity("Erro!");

FormData recolhe todos os campos do formulário. Use com fetch() para enviar. e.preventDefault() impede o submit padrão. input.validity dá acesso à validação nativa do browser.

Eventos — addEventListener
const btn = document.querySelector("#btn");

// Adicionar listener
btn.addEventListener("click", function(e) {
  console.log(e.target);       // elemento clicado
  console.log(e.type);         // "click"
  console.log(e.clientX);      // posição X
  e.preventDefault();          // impede ação padrão
  e.stopPropagation();         // impede bubbling
});

// Arrow function
btn.addEventListener("click", (e) => {
  console.log("clicado!");
});

// Opções
btn.addEventListener("click", handler, {
  once: true,       // remove após 1ª execução
  capture: true,    // fase de captura
  passive: true     // não chama preventDefault
});

// Remover
btn.removeEventListener("click", handler);

addEventListener() regista um handler. e.target é o elemento que originou o evento. preventDefault() impede a ação padrão. once: true auto-remove após a primeira execução.

IntersectionObserver (Lazy Load)
// Observar elementos ao entrar no viewport
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add("visivel");
      observer.unobserve(entry.target);
    }
  });
}, {
  threshold: 0.1,     // 10% visível
  rootMargin: "50px"  // margem extra
});

// Observar elementos
document.querySelectorAll(".lazy")
  .forEach(el => observer.observe(el));

// Lazy load de imagens
const imgObs = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      imgObs.unobserve(img);
    }
  });
});

IntersectionObserver deteta quando elementos entram no viewport. Ideal para lazy loading, animações ao scroll e infinite scroll. threshold define a % visível para ativar. unobserve() para de observar.

Async e Promises


11 cards
Promises — Básico
// Criar uma Promise
const promessa = new Promise((resolve, reject) => {
  const sucesso = true;
  if (sucesso) {
    resolve("Dados prontos");
  } else {
    reject(new Error("Falhou"));
  }
});

// Consumir
promessa
  .then(resultado => console.log(resultado))
  .catch(erro => console.error(erro))
  .finally(() => console.log("Sempre executa"));

// Estados: pending → fulfilled | rejected
// Uma Promise resolve apenas 1 vez

Uma Promise tem 3 estados: pending, fulfilled, rejected. resolve() cumpre, reject() rejeita. .then() trata sucesso, .catch() erro, .finally() sempre executa.

Promise.all e allSettled
// Executar em paralelo
const [users, posts] = await Promise.all([
  fetch("/api/users").then(r => r.json()),
  fetch("/api/posts").then(r => r.json())
]);

// allSettled: espera todas (mesmo com erro)
const resultados = await Promise.allSettled([
  fetch("/api/a"),
  fetch("/api/b")  // pode falhar
]);
resultados.forEach(r => {
  if (r.status === "fulfilled") console.log(r.value);
  else console.log(r.reason);
});

Promise.all() espera todas e falha se uma falhar. Promise.allSettled() espera todas sem falhar — retorna status de cada uma. Use all() quando todas são obrigatórias.

Web Workers (Básico)
// worker.js (ficheiro separado)
self.onmessage = (e) => {
  const resultado = e.data * 2;
  self.postMessage(resultado);
};

// main.js
const worker = new Worker("worker.js");

worker.onmessage = (e) => {
  console.log("Resultado:", e.data);
};

worker.postMessage(21);  // envia dados

// Terminar
worker.terminate();

// Inline worker (sem ficheiro)
const blob = new Blob([`
  self.onmessage = (e) => {
    self.postMessage(e.data.toUpperCase());
  };
`], { type: "application/javascript" });
const inline = new Worker(URL.createObjectURL(blob));

Web Workers executam código numa thread separada (não bloqueia a UI). Comunique via postMessage()/onmessage. Ideal para cálculos pesados. terminate() encerra o worker.

async/await
// Função async retorna Promise
async function buscarDados() {
  try {
    const res = await fetch("/api/dados");
    const dados = await res.json();
    return dados;
  } catch (erro) {
    console.error("Erro:", erro);
    throw erro;
  }
}

// Chamar
buscarDados()
  .then(dados => console.log(dados))
  .catch(e => console.error(e));

// Em arrow functions
const buscar = async () => {
  const res = await fetch("/api");
  return res.json();
};

// IIFE async
(async () => {
  const dados = await buscar();
  console.log(dados);
})();

async marca a função como assíncrona (retorna Promise). await pausa até a Promise resolver. Use try/catch para erros. Só pode usar await dentro de funções async (ou top-level em módulos).

Promise.race e any
// race: primeira a resolver (ou rejeitar)
const maisRapida = await Promise.race([
  fetch("/api/servidor1"),
  fetch("/api/servidor2")
]);

// any: primeira com sucesso (ignora erros)
const primeira = await Promise.any([p1, p2, p3]);

// Timeout com race
function comTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error("Timeout")), ms)
  );
  return Promise.race([promise, timeout]);
}
await comTimeout(fetch("/api"), 5000);

Promise.race() retorna a primeira a resolver OU rejeitar. Promise.any() retorna a primeira com sucesso (ignora erros). Útil para timeouts e tentar múltiplos servidores.

Event Loop e Microtasks
console.log("1 - Síncrono");

setTimeout(() => {
  console.log("2 - Macrotask (timeout)");
}, 0);

Promise.resolve().then(() => {
  console.log("3 - Microtask (promise)");
});

queueMicrotask(() => {
  console.log("4 - Microtask (queue)");
});

console.log("5 - Síncrono");

// Ordem: 1, 5, 3, 4, 2
// Síncrono → Microtasks → Macrotasks

O Event Loop processa: 1) código síncrono, 2) microtasks (Promises, queueMicrotask), 3) macrotasks (setTimeout, eventos). Mesmo com delay 0, setTimeout executa por último.

fetch — GET e Leitura
// GET simples
const res = await fetch("https://api.exemplo.com/dados");
const dados = await res.json();

// Verificar status
if (!res.ok) {
  throw new Error(`HTTP ${res.status}`);
}

// Headers e status
res.headers.get("content-type");
res.status;  // 200, 404, 500...

// DELETE
await fetch("/api/1", { method: "DELETE" });

fetch() retorna uma Promise. res.json() parseia o body. Verifique sempre res.ok (status 200-299) antes de usar os dados. res.status dá o código HTTP.

Encadeamento de Promises
// Encadear .then()
fetch("/api/user/1")
  .then(res => res.json())
  .then(user => fetch(`/api/posts/${user.id}`))
  .then(res => res.json())
  .then(posts => console.log(posts))
  .catch(err => console.error(err));

// Equivalente com async/await (mais legível)
async function getPosts() {
  const res = await fetch("/api/user/1");
  const user = await res.json();
  const res2 = await fetch(`/api/posts/${user.id}`);
  return res2.json();
}

// Retornar valor vs Promise
Promise.resolve(42)
  .then(n => n * 2)       // 84
  .then(n => Promise.resolve(n + 1))  // 85
  .then(console.log);

Cada .then() retorna uma nova Promise. Pode retornar um valor ou outra Promise. async/await é mais legível para operações sequenciais. Erros propagam até ao .catch().

Async Iterators (for await...of)
// Objeto com async iterator
const paginas = {
  async *[Symbol.asyncIterator]() {
    let page = 1;
    while (true) {
      const res = await fetch(`/api?page=${page}`);
      const dados = await res.json();
      if (!dados.length) return;
      yield dados;
      page++;
    }
  }
};

// Consumir com for await...of
for await (const items of paginas) {
  console.log("Página:", items);
}

// Stream de resposta fetch
const res = await fetch("/api/grande");
const reader = res.body.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  console.log("Chunk:", value.length, "bytes");
}

for await...of itera sobre async iterables. async *[Symbol.asyncIterator]() define um gerador assíncrono. Ideal para paginação, streams e leitura de ficheiros em chunks.

fetch — POST e Headers
// POST com JSON
const res = await fetch("/api/utilizadores", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer token123"
  },
  body: JSON.stringify({ nome: "Ana", idade: 30 })
});

const criado = await res.json();

// PUT (atualizar)
await fetch("/api/1", {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ nome: "Maria" })
});

// POST com FormData (upload)
const form = new FormData();
form.append("ficheiro", fileInput.files[0]);
await fetch("/upload", { method: "POST", body: form });

Para POST, defina Content-Type e use JSON.stringify() no body. Authorization envia tokens. Para uploads, use FormData sem definir header (o browser define automaticamente).

AbortController (Cancelar fetch)
const controller = new AbortController();
const { signal } = controller;

// Fetch com signal
const res = await fetch("/api/dados", { signal });

// Cancelar após 5s
const timeout = setTimeout(() => {
  controller.abort();
}, 5000);

try {
  const dados = await res.json();
  clearTimeout(timeout);
} catch (e) {
  if (e.name === "AbortError") {
    console.log("Requisição cancelada");
  }
}

// Cancelar em botão
const btn = document.querySelector("#cancelar");
btn.addEventListener("click", () => {
  controller.abort();
});

AbortController cancela requisições fetch(). Passe signal nas opções. controller.abort() cancela. O erro tem name === "AbortError". Útil para timeouts e cancelar requests ao navegar.

ES6+ Moderno


6 cards
Modules (import/export)
// utils.js — Named exports
export const PI = 3.14159;
export function soma(a, b) { return a + b; }
export class Calculadora { /* ... */ }

// app.js — Named imports
import { PI, soma } from "./utils.js";
import { soma as add } from "./utils.js";  // alias
import * as utils from "./utils.js";  // namespace
utils.PI;  // 3.14159

// Default export (1 por ficheiro)
// api.js
export default class Api { /* ... */ }

// Import default (qualquer nome)
import Api from "./api.js";
import MinhaApi from "./api.js";  // mesmo resultado

// Dynamic import (lazy loading)
const modulo = await import("./pesado.js");
modulo.funcao();

export exporta, import importa. Named exports permitem vários por ficheiro. Default export só 1 por ficheiro. import() dinâmico carrega sob demanda (code splitting). Use .js no path.

WeakMap, WeakSet e Symbol
// Symbol: identificador único
const id = Symbol("id");
const obj = { [id]: 123 };
obj[id];  // 123

// Symbols bem conhecidos
const arr = [3, 1, 2];
arr[Symbol.iterator]();  // iterador

// WeakMap: chaves fracas (GC pode limpar)
const cache = new WeakMap();
function processar(obj) {
  if (cache.has(obj)) return cache.get(obj);
  const resultado = /* cálculo pesado */ obj;
  cache.set(obj, resultado);
  return resultado;
}

// WeakSet: objetos únicos (fracos)
const vistos = new WeakSet();
vistos.add(elemento);
vistos.has(elemento);  // true

Symbol cria identificadores únicos. WeakMap/WeakSet têm chaves fracas — o Garbage Collector pode limpar entradas se a chave for coletada. Ideais para caches e metadados privados.

Classes
class Animal {
  // Campos privados (ES2022)
  #energia = 100;
  
  constructor(nome, som) {
    this.nome = nome;
    this.som = som;
  }
  
  falar() {
    return `${this.nome} faz ${this.som}`;
  }
  
  // Getter/Setter
  get energia() { return this.#energia; }
  set energia(val) { this.#energia = Math.max(0, val); }
  
  // Método estático
  static criar(nome) {
    return new Animal(nome, "...");
  }
}

// Herança
class Cao extends Animal {
  constructor(nome) {
    super(nome, "Au au");
  }
  
  falar() {
    return `${super.falar()}!`;
  }
}

class define classes. constructor() inicializa. #campo é privado. extends/super() para herança. get/set criam accessors. static pertence à classe, não à instância.

Top-level Await e using
// Top-level await (ES2022, em módulos)
// Não precisa de IIFE async!
const dados = await fetch("/api/config")
  .then(r => r.json());

// Import dinâmico condicional
const modulo = process.env.DEBUG
  ? await import("./debug.js")
  : await import("./producao.js");

// using (ES2024 - resource management)
// using declara recursos que são limpos automaticamente
function abrirFicheiro() {
  return {
    [Symbol.dispose]() {
      console.log("Ficheiro fechado");
    }
  };
}

{
  using ficheiro = abrirFicheiro();
  // usar ficheiro...
}  // Symbol.dispose() chamado automaticamente

Top-level await permite await fora de funções async (só em módulos ES). using (ES2024) declara recursos com limpeza automática via Symbol.dispose() — similar ao using do C# ou with do Python.

Generators e Iterators
// Generator function
function* contador(max) {
  let i = 0;
  while (i < max) {
    yield i++;
  }
}

const gen = contador(3);
gen.next();  // { value: 0, done: false }
gen.next();  // { value: 1, done: false }
gen.next();  // { value: 2, done: false }
gen.next();  // { value: undefined, done: true }

// Iterável com for...of
for (const n of contador(5)) {
  console.log(n);  // 0, 1, 2, 3, 4
}

// Lazy evaluation (infinito)
function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

function* cria um generator. yield pausa e retorna valor. .next() retoma. São lazy — só calculam quando pedido. Ideais para sequências infinitas e pipelines de dados.

Proxy e Reflect
// Proxy: intercepta operações
const handler = {
  get(target, prop) {
    console.log(`Acedeu: ${prop}`);
    return Reflect.get(target, prop);
  },
  set(target, prop, value) {
    if (typeof value !== "number") {
      throw new TypeError("Só números!");
    }
    return Reflect.set(target, prop, value);
  }
};

const dados = new Proxy({ x: 1 }, handler);
dados.x;      // log + 1
dados.x = 5;  // OK
dados.x = "a";  // TypeError!

// Validação reativa
const state = new Proxy({ count: 0 }, {
  set(t, p, v) {
    Reflect.set(t, p, v);
    render();  // re-renderiza
    return true;
  }
});

Proxy intercepta operações (get, set, delete, etc.) num objeto. Reflect fornece os métodos padrão. Útil para validação, logging, reatividade e data binding. Base de frameworks como Vue.

Erros e Debug


6 cards
try/catch/finally
try {
  const dados = JSON.parse(texto);
  if (!dados.nome) {
    throw new Error("Campo 'nome' obrigatório");
  }
} catch (erro) {
  console.error("Tipo:", erro.name);     // "Error"
  console.error("Msg:", erro.message);   // mensagem
  console.error("Stack:", erro.stack);   // trace
} finally {
  console.log("Sempre executa");
  // Fechar conexões, limpar recursos
}

// Em async/await
async function carregar() {
  try {
    const res = await fetch("/api");
    return await res.json();
  } catch (e) {
    console.error("Falha:", e.message);
    return null;
  }
}

try executa código arriscado. catch captura erros. finally executa sempre (limpeza). throw new Error() lança erros personalizados. Em async, use try/catch em vez de .catch().

Tratamento Global de Erros
// Erros não capturados
window.addEventListener("error", (e) => {
  console.error("Erro global:", e.message);
  console.error("Ficheiro:", e.filename);
  console.error("Linha:", e.lineno);
  // Enviar para serviço de logging
});

// Promises rejeitadas sem catch
window.addEventListener("unhandledrejection", (e) => {
  console.error("Promise rejeitada:", e.reason);
  e.preventDefault();  // não mostrar no console
});

// Error boundary em componentes (React-like)
class ErrorBoundary {
  static capturar(fn, onError) {
    try { return fn(); }
    catch (e) { onError(e); return null; }
  }
}

window.onerror captura erros não tratados. unhandledrejection captura Promises sem .catch(). Útil para logging global e reportar erros a serviços como Sentry. Não substitui try/catch local.

Tipos de Erros
// TypeError: tipo errado
null.toString();        // TypeError
"str"();               // TypeError

// ReferenceError: variável não existe
console.log(x);        // ReferenceError

// SyntaxError: código inválido
eval("if (");           // SyntaxError

// RangeError: valor fora do limite
new Array(-1);          // RangeError
(1).toFixed(101);       // RangeError

// URIError: URI inválida
decodeURIComponent("%");  // URIError

// Erro personalizado
class ValidationError extends Error {
  constructor(campo, msg) {
    super(msg);
    this.name = "ValidationError";
    this.campo = campo;
  }
}
throw new ValidationError("email", "Inválido");

Tipos nativos: TypeError (tipo errado), ReferenceError (não existe), SyntaxError (código inválido), RangeError (fora do limite). Crie classes com extends Error para erros custom.

AggregateError e Error Cause
// Error cause (ES2022)
try {
  await fetch("/api");
} catch (e) {
  throw new Error("Falha ao carregar dados", { cause: e });
}
// error.cause contém o erro original

// AggregateError: agrupar múltiplos erros
const erros = [
  new Error("Campo nome obrigatório"),
  new Error("Email inválido"),
  new Error("Idade mínima: 18")
];

throw new AggregateError(erros, "Validação falhou");

// Promise.any lança AggregateError se todas falharem
try {
  await Promise.any([p1, p2, p3]);
} catch (e) {
  e.errors;  // array de todos os erros
}

Error cause ({ cause: e }) encadeia erros mantendo o original. AggregateError agrupa múltiplos erros num só. Promise.any() lança AggregateError quando todas as promises falham.

Error Boundary (Padrão)
// Wrapper genérico para operações arriscadas
function tentar(fn, fallback = null) {
  try {
    return { dados: fn(), erro: null };
  } catch (e) {
    return { dados: fallback, erro: e };
  }
}

// Uso
const { dados, erro } = tentar(() => {
  return JSON.parse(textoInvalido);
});

if (erro) {
  console.error("Parse falhou:", erro.message);
}

// Async version
async function tentarAsync(fn, fallback = null) {
  try {
    return { dados: await fn(), erro: null };
  } catch (e) {
    return { dados: fallback, erro: e };
  }
}

const { dados: user } = await tentarAsync(
  () => fetch("/api/user").then(r => r.json())
);

Padrão try/catch encapsulado: retorna { dados, erro } em vez de lançar. Evita try/catch repetido. Inspirado no padrão Result de linguagens funcionais. Versão async para Promises.

Debugging com DevTools
// debugger: pausa execução no DevTools
function calcular(x) {
  debugger;  // breakpoint manual
  return x * 2;
}

// console.dir: ver estrutura do objeto
console.dir(document.body);

// Monitorar chamadas
const obj = { metodo() {} };
console.log(obj.metodo.toString());

// Performance
performance.now();  // timestamp preciso
performance.mark("inicio");
// ... código ...
performance.mark("fim");
performance.measure("op", "inicio", "fim");

// Ver medidas
performance.getEntriesByType("measure");

// Memory
// Chrome DevTools > Memory > Heap Snapshot

debugger; cria um breakpoint no código. performance.now() dá timestamps precisos. performance.mark()/measure() medem trechos. Use o DevTools para breakpoints condicionais e watch expressions.

Truques e Dicas


9 cards
Desestruturação com Defaults
// Config com defaults
function criarServer({
  host = "localhost",
  port = 3000,
  ssl = false,
  db = { host: "localhost", port: 5432 }
} = {}) {
  console.log(`${host}:${port}`);
}

criarServer();  // localhost:3000
criarServer({ port: 8080, ssl: true });

// Parâmetros opcionais com objeto
function buscar({ query = "", page = 1, limit = 10 } = {}) {
  return `/api?q=${query}&p=${page}&l=${limit}`;
}

// Extrair com defaults
const { nome = "Anónimo", role = "user" } = getUser();

Padrão de configuração: objeto com defaults + = {} para permitir chamar sem argumentos. Mais legível que parâmetros posicionais. Ideal para funções com muitas opções opcionais.

Encadeamento e Imutabilidade
// NÃO mutar — criar novo
const state = { items: [1, 2, 3], filtro: "todos" };

// Adicionar item
const novo = { ...state, items: [...state.items, 4] };

// Remover item
const sem2 = {
  ...state,
  items: state.items.filter(i => i !== 2)
};

// Atualizar item
const atualizado = {
  ...state,
  items: state.items.map(i =>
    i === 2 ? { ...i, done: true } : i
  )
};

// toSorted, toReversed, toSpliced (ES2023)
const ordenado = [3,1,2].toSorted();  // não muta!
const invertido = [1,2,3].toReversed();
const semPrimeiro = [1,2,3].toSpliced(0, 1);

Imutabilidade: crie novos objetos/arrays em vez de mutar. spread + map/filter para transformações. ES2023: toSorted(), toReversed(), toSpliced() não mutam o original.

URLSearchParams e Query Strings
// Ler query string atual
const params = new URLSearchParams(window.location.search);
params.get("page");     // "2"
params.has("filtro");   // true/false
params.getAll("tag");   // ["js", "web"]

// Criar e modificar
const p = new URLSearchParams();
p.set("q", "javascript");
p.set("page", "1");
p.append("tag", "es6");
p.toString();  // "q=javascript&page=1&tag=es6"

// Atualizar URL sem reload
const url = new URL(window.location);
url.searchParams.set("page", "2");
history.pushState({}, "", url);

// Parse de URL completa
const u = new URL("https://api.com/dados?page=2&limit=10");
u.hostname;     // "api.com"
u.pathname;     // "/dados"
u.searchParams.get("page");  // "2"

URLSearchParams manipula query strings facilmente. history.pushState() atualiza a URL sem recarregar. new URL() parseia URLs completas. Essencial para filtros, paginação e partilha de estado via URL.

Operações Úteis em Arrays
// Último elemento
const ultimo = arr.at(-1);

// Remover duplicados
const unico = [...new Set(arr)];

// Achatar
[[1,2],[3,4]].flat();       // [1,2,3,4]
[[1,[2,[3]]]].flat(Infinity);  // [1,2,3]

// Agrupar (ES2024)
const items = [
  { tipo: "fruta", nome: "maçã" },
  { tipo: "legume", nome: "cenoura" },
  { tipo: "fruta", nome: "pera" }
];
const grupos = Object.groupBy(items, i => i.tipo);

// Chunk (dividir em grupos)
function chunk(arr, size) {
  return Array.from({ length: Math.ceil(arr.length / size) },
    (_, i) => arr.slice(i * size, (i + 1) * size));
}
chunk([1,2,3,4,5], 2);  // [[1,2],[3,4],[5]]

.at(-1) acede ao último elemento. [...new Set()] remove duplicados. .flat(Infinity) achata totalmente. Object.groupBy() agrupa por chave. chunk() divide em grupos de tamanho fixo.

Sleep e Retry
// Sleep (pausa async)
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
await sleep(1000);  // pausa 1s

// Retry com tentativas
async function retry(fn, tentativas = 3) {
  for (let i = 0; i < tentativas; i++) {
    try { return await fn(); }
    catch (e) {
      if (i === tentativas - 1) throw e;
      await sleep(1000 * (i + 1));
    }
  }
}

// Uso
const dados = await retry(() => fetch("/api").then(r => r.json()));

sleep() pausa código async sem bloquear a thread. retry() repete operações que podem falhar com delay crescente (backoff). Essencial para chamadas de rede instáveis.

Debounce e Throttle
// Debounce: espera X ms após última chamada
function debounce(fn, delay = 300) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

// Uso: pesquisa ao digitar
input.addEventListener("input", debounce((e) => {
  buscar(e.target.value);
}, 500));

// Throttle: no máximo 1x a cada X ms
function throttle(fn, limit = 200) {
  let ultimo = 0;
  return (...args) => {
    const agora = Date.now();
    if (agora - ultimo >= limit) {
      ultimo = agora;
      fn(...args);
    }
  };
}

// Uso: scroll
window.addEventListener("scroll", throttle(() => {
  verificarPosicao();
}, 100));

Debounce espera X ms após a última chamada (pesquisa ao digitar). Throttle limita a 1 execução por X ms (scroll, resize). Ambos evitam excesso de chamadas a funções pesadas.

Memoize e Cache
// Memoize (cache de resultados)
function memoize(fn) {
  const cache = new Map();
  return (...args) => {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const resultado = fn(...args);
    cache.set(key, resultado);
    return resultado;
  };
}

// Uso: Fibonacci com cache
const fib = memoize((n) => n <= 1 ? n : fib(n-1) + fib(n-2));
fib(40);  // rápido!

// Cache simples com Map
const cache = new Map();
async function getCached(url) {
  if (cache.has(url)) return cache.get(url);
  const res = await fetch(url).then(r => r.json());
  cache.set(url, res);
  return res;
}

memoize() guarda resultados em cache — ideal para funções puras com cálculos pesados. Use Map como cache para requisições repetidas. Só funciona com funções determinísticas (mesmo input = mesmo output).

Deep Clone e Comparação
// Deep clone moderno
const original = { a: 1, b: { c: [1, 2] } };
const copia = structuredClone(original);
copia.b.c.push(3);
original.b.c;  // [1, 2] (inalterado!)

// JSON (não copia funções, Date, undefined)
const copia2 = JSON.parse(JSON.stringify(original));

// Comparar objetos (shallow)
const a = { x: 1, y: 2 };
const b = { x: 1, y: 2 };
a === b;  // false (referências diferentes)

// Shallow compare
const igual = JSON.stringify(a) === JSON.stringify(b);

// structuredClone suporta:
// Date, RegExp, Map, Set, ArrayBuffer, etc.

structuredClone() faz deep clone nativo (suporta Date, Map, Set). JSON.parse(JSON.stringify()) é limitado (perde funções, Date vira string). Objetos comparam-se por referência, não valor.

Regex — Essencial
// Testar e extrair
const email = "ana@exemplo.com";
/^[\w.-]+@[\w.-]+\.\w+$/.test(email);  // true

// Capturar grupos
const match = "2024-01-15".match(/(\d{4})-(\d{2})-(\d{2})/);
match[1];  // "2024"
match[2];  // "01"
match[3];  // "15"

// Named groups
const { groups } = "2024-01-15".match(
  /(?<ano>\d{4})-(?<mes>\d{2})-(?<dia>\d{2})/
);
groups.ano;  // "2024"

// Replace com regex
"10.50".replace(/\./g, ",");  // "10,50"
"olá mundo".replace(/\b\w/g, c => c.toUpperCase());
// "Olá Mundo"

// Split
"a,b,,c".split(/,+/);  // ["a","b","","c"]

.test() verifica se corresponde. .match() extrai grupos com (). (?<nome>) cria named groups. .replace() com /g substitui todas. \d = dígito, \w = palavra, + = 1 ou mais.