DevTools

Cheatsheet JavaScript

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

Volver a los lenguajes
JavaScript
93 tarjetas encontradas
Categorías:
Versiones:

Básico


13 cards
Declaración de Variables
let nombre = "Ana";        // mutable, alcance de bloque
const PI = 3.14159;      // constante, no reasignable
var antiguo = "evitar";   // alcance de función (legacy)

// let puede ser reasignado
nombre = "Maria";

// const no puede ser reasignado
// PI = 3;  // TypeError!

// const con objetos: la referencia es fija
const user = { nombre: "John" };
user.nombre = "Pedro";  // OK - muta el contenido
// user = {};         // TypeError!

Usa const por defecto y let cuando necesites reasignar. Evita var — tiene alcance de función y causa bugs con hoisting. const no impide la mutación de objetos, solo la reasignación de la referencia.

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

// Todo lo demás es TRUTHY:
true, 1, -1, "texto", [], {}, "0", "false"

// Ejemplos prácticos
if ("") { }       // no se ejecuta (falsy)
if ("texto") { }  // se ejecuta (truthy)
if ([]) { }       // ¡se ejecuta! (array vacío es truthy)
if ({}) { }       // ¡se ejecuta! (objeto vacío es truthy)

// Comprobar booleano real
Boolean("");     // false
Boolean("oi");   // true
!!0;             // false (doble negación)
!!"texto";       // true

// Uso común: valor por defecto
let nombre = input || "Anónimo";

JavaScript tiene 6 valores falsy: false, 0, "", null, undefined y NaN. Todo lo demás es truthy, incluyendo arrays y objetos vacíos. Usa !! para convertir a booleano real.

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

// Valores por defecto
const [nombre = "Anónimo"] = [];

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

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

// En parámetros de función
function mostrar({ nombre, edad }) {
  console.log(`${nombre}, ${edad}`);
}
mostrar({ nombre: "Ana", edad: 30 });

// Intercambiar variables
let x = 1, y = 2;
[x, y] = [y, x];  // x=2, y=1

El destructuring extrae valores de arrays o propiedades de objetos en variables. Soporta valores por defecto, renombrado ({ name: n }), anidación y rest. Muy usado en parámetros de funciones.

typeof e instanceof
// typeof: devuelve una string con el tipo
typeof "texto";     // "string"
typeof 42;          // "number"
typeof true;        // "boolean"
typeof undefined;   // "undefined"
typeof {};          // "object"
typeof [];          // "object" (¡no distingue!)
typeof null;        // "object" (¡bug!)
typeof function(){};// "function"
typeof Symbol();    // "symbol"

// instanceof: comprueba el prototipo
[] instanceof Array;   // true
[] instanceof Object;  // true
new Date() instanceof Date;  // true

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

// Comprobar null
const isNull = val === null;
const isNil = val == null;  // null O undefined

typeof devuelve el tipo como string pero falla con arrays y null. Usa Array.isArray() para arrays y === null para null. instanceof comprueba la cadena de prototipos.

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

// Comprobar 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 tiene 7 tipos primitivos. typeof null devuelve "object" — es un bug histórico del lenguaje. Usa === null para comprobar null explícitamente.

Conversión de Tipos
// String → Number
Number("42");       // 42
parseInt("42px");   // 42
parseFloat("3.14"); // 3.14
+"42";              // 42 (unario)

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

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

// Coerción implícita (¡evitar!)
"5" + 3;    // "53" (¡concatena!)
"5" - 3;    // 2    (¡convierte!)
true + 1;   // 2
[] + {};    // "[object Object]"

// Comprobar NaN
isNaN(NaN);         // true
Number.isNaN(NaN);  // true (más seguro)

Usa Number(), parseInt() o el unario + para convertir strings en numeros. Cuidado con la coerción implícita: "5" + 3 concatena ("53") pero "5" - 3 convierte (2). Prefiere Number.isNaN() en lugar de isNaN().

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

// Formato CSS
console.log("%c Texto azul", "color: blue");

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

// Objeto con profundidad
console.dir(obj, { depth: null });

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

// Assert
console.assert(1 === 2, "1 no es 2!");

Además de console.log(), usa console.table() para arrays/objetos, console.dir() para inspeccionar la estructura y console.group() para organizar logs relacionados.

Operadores de Comparación
// == vs === (SIEMPRE usa ===)
5 == "5";      // true  (¡coerción de tipo!)
5 === "5";     // false (sin coerción)
null == undefined;  // true
null === undefined; // false

// Operadores relacionales
10 > 5;       // true
10 <= 10;     // true
"a" < "b";    // true (orden alfabético)

// 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 (¡no es null!)

Usa siempre === y !== en lugar de == y != para evitar la coerción implícita. El operador ?? (nullish) solo se activa con null o undefined, a diferencia de || que también se activa con 0 y "".

Optional Chaining (?.)
const user = {
  nombre: "Ana",
  dirección: {
    calle: "Av. Central",
    ciudad: "Lisbon"
  }
};

// Sin optional chaining (verboso)
const ciudad = user && user.dirección && user.dirección.ciudad;

// Con optional chaining
const cidade2 = user?.dirección?.ciudad;  // "Lisbon"
const país = user?.dirección?.país;       // undefined (¡sin error!)

// Con métodos
const upper = user?.getName?.();  // undefined si no existe

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

// Combinar con nullish
const nombre = user?.nombre ?? "Anónimo";

El ?. (optional chaining) accede a propiedades sin error si el objeto es null/undefined. Devuelve undefined en lugar de lanzar TypeError. Combínalo con ?? para valores por defecto.

console — Rendimiento y Conteo
// Medir tiempo
console.time("operación");
// ... código ...
console.timeEnd("operación");  // "operación: 2.5ms"

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

// Trace (pila de llamadas)
function a() { b(); }
function b() { console.trace(); }
a();  // muestra la pila de llamadas

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

console.time()/timeEnd() mide el rendimiento. console.count() cuenta llamadas. console.trace() muestra la pila de llamadas. debugger; crea un breakpoint en el código.

Template Literals
const nombre = "Ana";
const edad = 30;

// Interpolación con comillas invertidas
const msg = `Hola, ${nombre}! Tienes ${edad} anios.`;

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

// Multi-línea
const html = `
  <div>
    <h1>${nombre}</h1>
    <p>Edad: ${edad}</p>
  </div>
`;

// Función dentro
const upper = `Nombre: ${nombre.toUpperCase()}`;

// Ternario dentro
const status = `${edad >= 18 ? "Adulto" : "Menor"}`;

Los template literals usan comillas invertidas ` y permiten interpolación con ${expresión}. Soportan multi-línea sin \n y aceptan cualquier expresión JavaScript dentro de ${}.

Spread y 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: recoger argumentos
function suma(...nums) {
  return nums.reduce((a, b) => a + b, 0);
}
suma(1, 2, 3, 4);  // 10

// Destructuring con 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}

El operador ... tiene dos usos: spread expande arrays/objetos y rest recoge los argumentos restantes. Crea copias superficiales (shallow) — los objetos anidados comparten referencia.

Operador Ternario
// Sintaxis: condición ? valorSiTrue : valorSiFalse
const edad = 20;
const status = edad >= 18 ? "Adulto" : "Menor";

// Encadenado (evitar más de 2 niveles)
const nota = 15;
const resultado = nota >= 18 ? "Excelente"
  : nota >= 14 ? "Bom"
  : nota >= 10 ? "Suficiente"
  : "Insuficiente";

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

// Con nullish para valores por defecto
const nombre = user?.nombre ?? "Anónimo";

// Asignación condicional
let msg;
if (isAdmin) msg = "Bienvenido admin";
else msg = "Bienvenido";
// Equivalente:
const msg2 = isAdmin ? "Bienvenido admin" : "Bienvenido";

El operador ternario condición ? a : b es una expresión condicional compacta. Ideal para asignaciones simples. Evita encadenar más de 2 niveles — usa if/else para lógica compleja.

Strings


6 cards
Búsqueda y Verificación
const str = "Hola Mundo JavaScript";

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

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

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

indexOf() devuelve la posición (o -1). includes(), startsWith() y endsWith() devuelven un booleano. at(-1) accede al último carácter sin calcular el índice.

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

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

// Iterar con for...of (respeta Unicode)
for (const char of "Hola 😀") {
  console.log(char);
}

// Normalización
"café".normalize("NFC");

// Escape
"linha1\nlinha2";   // nueva línea
"tab\taqui";        // tabulación
"aspas \"aqui\"";   // comillas

// String.raw (sin escape)
String.raw`C:\nuevo\carpeta`;  // "C:\nuevo\carpeta"

// Comparación locale
"a".localeCompare("b");  // -1
"ã".localeCompare("a");  // 1

Usa for...of para iterar strings con emoji (respeta los surrogate pairs). String.raw ignora las secuencias de escape. localeCompare() compara strings con soporte de acentos.

Transformación de Strings
const str = "Hola Mundo JavaScript";

str.toUpperCase();       // "HOLA MUNDO JAVASCRIPT"
str.toLowerCase();       // "hola mundo javascript"
str.slice(4, 9);         // "Mundo"
str.substring(4, 9);     // "Mundo"
str.replace("Mundo", "JS");  // "Hola JS JavaScript"
str.replaceAll("a", "o");    // "Hola Mundo JovaScript"
str.trim();              // elimina espacios en los extremos
str.repeat(2);           // repite la string
str.charAt(0);           // "O"

// concat
"Hola".concat(" ", "Mundo");  // "Hola Mundo"

Las strings son inmutables — los métodos devuelven nuevas strings. slice() acepta índices negativos, replace() solo sustituye la primera aparición (usa replaceAll() para todas).

localeCompare y Normalización
// Comparación con locale
"a".localeCompare("b");      // -1
"ã".localeCompare("a", "pt"); // 1 (después)
"Agua".localeCompare("banana", "pt"); // -1

// Ordenar con acentos
["acción", "Agua", "banana"].sort(
  (a, b) => a.localeCompare(b, "pt")
);
// ["Agua", "acción", "banana"]

// Normalización Unicode
"café".normalize("NFC");   // forma compuesta
"café".normalize("NFD");   // forma descompuesta

// Comprobar igualdad visual
const s1 = "\u00E9";       // es (compuesto)
const s2 = "e\u0301";      // es (descompuesto)
s1 === s2;                  // ¡false!
s1.normalize() === s2.normalize();  // true

localeCompare() compara strings respetando el idioma y los acentos. normalize() uniformiza las representaciones Unicode — esencial para comparar strings con acentos de distintas fuentes.

split, padStart y search
// split: string → array
"a,b,c".split(",");       // ["a","b","c"]
"hola mundo".split(" ");   // ["hola","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 (con regex)
"Hola Mundo".search(/mundo/i);  // 4

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

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

// concat
"Hola".concat(" ", "Mundo");  // "Hola Mundo"

split() divide una string en un array por delimitador. padStart()/padEnd() rellenan hasta un tamaño. at(-1) accede al último carácter sin calcular el índice.

Regex Básico
// Creación
const re1 = /patrón/gi;
const re2 = new RegExp("patrón", "gi");

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

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

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

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

// Metacaracteres comunes
// \d = dígito, \w = palabra, \s = espacio
// . = cualquiera, ^ = inicio, $ = fin
// + = 1+, * = 0+, ? = 0 o 1
// {n,m} = entre n y m

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

Las regex en JS usan /patrón/flags. .test() comprueba si hay coincidencia, .match() extrae grupos y .replace() sustituye. Metacaracteres: \d (dígito), \w (palabra), ^/$ (inicio/fin).

Números e Math


5 cards
Redondeo y Potencias
// Redondeo
Math.round(4.5);    // 5
Math.floor(4.9);    // 4 (hacia abajo)
Math.ceil(4.1);     // 5 (hacia arriba)
Math.trunc(4.9);    // 4 (corta el decimal)

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

// Potencia y raíz
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() redondea hacia abajo, Math.ceil() hacia arriba, Math.round() al más próximo. Usa ** en lugar de Math.pow() para potencias. Math.max(...arr) con spread para arrays.

Sistemas Numéricos
// Binario (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

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

// Parsear desde otras bases
parseInt("ff", 16);   // 255
parseInt("1010", 2);  // 10

Prefijos: 0b binario, 0o octal, 0x hexadecimal. toString(base) convierte a string en otra base. parseInt(str, base) parsea desde otra base. Los guiones bajos (1_000) mejoran la legibilidad.

Aleatorio y Constantes
// Aleatorio
Math.random();              // 0 a 0.999...
Math.floor(Math.random() * 10);  // 0 a 9

// Entero entre min y 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...

// Redondear con decimales
function roundDec(num, casas) {
  const f = 10 ** casas;
  return Math.round(num * f) / f;
}
roundDec(3.14159, 2);  // 3.14

Math.random() devuelve un float entre 0 y 1. Math.floor(Math.random() * n) genera enteros de 0 a n-1. Math.PI y Math.E son constantes. Para decimales, multiplica, redondea y divide.

Formato de Números
const num = 1234.5678;

// Decimales
num.toFixed(2);       // "1234.57" (¡string!)
num.toPrecision(4);   // "1235"

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

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

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

// Notación científica
(1234567).toExponential(2);  // "1.23e+6"

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

toFixed() devuelve una string — usa Number() o + para convertir de vuelta. toLocaleString() formatea con los separadores y la moneda del locale. parseInt() acepta la base como 2º argumento.

Number: Métodos Estáticos
// Comprobación
Number.isInteger(42);      // true
Number.isInteger(4.5);     // false
Number.isFinite(Infinity); // false
Number.isNaN(NaN);         // true
Number.isNaN("42");        // false (¡sin coerción!)

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

// Comparación de floats
0.1 + 0.2 === 0.3;  // ¡false!
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON;  // true

// BigInt (numeros enormes)
const big = 9007199254740993n;
big + 1n;  // 9007199254740994n
// big + 1;  // ¡TypeError! (mezclar tipos)

Number.isNaN() no hace coerción (a diferencia del isNaN() global). Los floats tienen imprecisión: 0.1 + 0.2 !== 0.3. Usa BigInt (sufijo n) para enteros por encima de MAX_SAFE_INTEGER.

Arrays


8 cards
Crear y Acceder
// Creación
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]

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

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

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

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

Array.from() convierte iterables en arrays. at(-1) accede al último elemento sin arr[arr.length-1]. Array.from({length: n}, fn) crea arrays con valores generados.

find, some y every
const users = [
  { nombre: "Ana", edad: 25 },
  { nombre: "Pedro", edad: 30 },
  { nombre: "Maria", edad: 35 },
];

// find: el primero que satisface
users.find(u => u.edad > 28);
// { nombre: "Pedro", edad: 30 }

// findIndex: índice del primero
users.findIndex(u => u.nombre === "Maria");  // 2

// some: ¿ALGUNO satisface?
users.some(u => u.edad > 30);   // true

// every: ¿TODOS satisfacen?
users.every(u => u.edad > 20);  // true
users.every(u => u.edad > 30);  // false

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

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

find() devuelve el primer elemento que satisface la condición. some() comprueba si alguno pasa, every() si todos pasan. findLast() búsqueda del final al inicio.

Añadir y Eliminar
const arr = [1, 2, 3];

// Añadir
arr.push(4);       // [1,2,3,4] (final)
arr.unshift(0);    // [0,1,2,3,4] (inicio)

// Eliminar
arr.pop();         // elimina el último → 4
arr.shift();       // elimina el primero → 0

// splice: eliminar/insertar en posición
arr.splice(1, 2);       // elimina 2 desde el índice 1
arr.splice(1, 0, 9, 8); // inserta 9,8 en el índice 1

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

// concat (no muta)
[1,2].concat([3,4]);  // [1,2,3,4]
[...[1,2], ...[3,4]]; // [1,2,3,4] (spread)

// flat: aplanar
[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 operan al final, unshift/shift al inicio. splice() muta el array original; slice() devuelve una copia sin mutar. flat(Infinity) aplana completamente.

Ordenación
// ¡sort() MUTA el array original!
const nums = [10, 5, 40, 25];

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

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

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

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

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

// Locale compare (acentos)
["acción","banana","Agua"].sort(
  (a, b) => a.localeCompare(b, "pt")
);

sort() muta el array original — usa [...arr].sort() o toSorted() (ES2023) para no mutar. Sin comparador, ordena como strings ("40" < "5"). Usa localeCompare() para los acentos.

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

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

// filter: mantiene los que pasan la prueba
const pares = nums.filter(n => n % 2 === 0);
// [2, 4]

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

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

map() transforma cada elemento y devuelve un nuevo array. filter() selecciona los que pasan la prueba. Ninguno muta el original. Encadénalos para pipelines de transformación.

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

// forEach (no devuelve 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 (rendimiento)
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() no permite break — usa for...of cuando necesites detener la iteración. entries() da índice + valor. flatMap() es map() + flat(1) combinados.

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

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

// Reduce a 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 los elementos en un único valor. El 2º argumento es el valor inicial del acumulador. Úsalo para sumar, agrupar o transformar en un objeto. flatMap() es map() + flat(1) combinados.

Métodos ES2023 (No Mutables)
const arr = [3, 1, 4, 1, 5];

// toSorted (¡no muta!)
const ordenado = arr.toSorted((a, b) => a - b);
// [1, 1, 3, 4, 5] | arr sin cambios

// toReversed (¡no muta!)
const invertido = arr.toReversed();
// [5, 1, 4, 1, 3]

// toSpliced (¡no muta!)
const sem2 = arr.toSpliced(1, 2);
// [3, 1, 5] | elimina 2 desde el índice 1

// with (sustituir 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() y with() devuelven nuevos arrays sin mutar el original. findLast() búsqueda del final al inicio.

Objetos


6 cards
Crear y Manipular Objetos
// Literal
const obj = { nombre: "Ana", edad: 30 };

// Acceso
obj.nombre;           // "Ana"
obj["edad"];       // 30

// Añadir/modificar
obj.email = "ana@mail.com";
obj.edad = 31;

// Eliminar
delete obj.email;

// Comprobar
"nombre" in obj;              // true
obj.hasOwnProperty("nombre"); // true

// Claves, valores, entradas
Object.keys(obj);    // ["nombre", "edad"]
Object.values(obj);  // ["Ana", 31]
Object.entries(obj); // [["nombre","Ana"],["edad",31]]

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

Los objetos son colecciones de pares clave-valor. Object.keys(), Object.values() y Object.entries() devuelven arrays iterables. delete elimina una propiedad.

JSON
const obj = { nombre: "Ana", edad: 30, activo: true };

// Objeto → JSON string
const json = JSON.stringify(obj);
// '{"nombre":"Ana","edad":30,"activo":true}'

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

// Con filtro de claves
JSON.stringify(obj, ["nombre", "edad"]);

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

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

// Cuidado: no soporta
// undefined, funciones, Symbol, Date (se vuelve string)
JSON.stringify(new Date());  // "2024-01-15T..."

JSON.stringify() convierte un objeto en una string JSON. JSON.parse() hace lo inverso. No soporta undefined, funciones ni Symbol. Las fechas se vuelven strings ISO.

Object.assign y 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 };           // igual (spread)

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

// Congelar (inmutable)
const config = Object.freeze({ port: 3000 });
config.port = 8080;  // ignorado (strict: error)
config.port;         // 3000

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

// Seal (no añade/elimina, pero edita)
const sealed = Object.seal({ a: 1 });
sealed.a = 2;    // OK
sealed.b = 3;    // ignorado

// Comparar objetos
Object.is(NaN, NaN);  // true (¡=== da false!)

Object.assign() y spread hacen un merge shallow. Object.freeze() lo hace inmutable (no añade, elimina ni edita). Object.is() es como === pero trata NaN correctamente.

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

// Clave dinámica en método
const método = "saudar";
const persona = {
  [método]() { return "Hola!"; }
};
persona.saudar();  // "Hola!"

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

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

Las computed properties ([expresión]) permiten claves dinámicas. Object.fromEntries() es el inverso de Object.entries() — convierte pares en un objeto. Útil para transformar valores de objetos.

Getters y Setters
const persona = {
  _nombre: "Ana",
  _edad: 30,

  get nombre() {
    return this._nombre.toUpperCase();
  },

  set nombre(val) {
    if (val.length < 2) throw new Error("Nombre corto");
    this._nombre = val;
  },

  get info() {
    return `${this._nombre}, ${this._edad} anios`;
  }
};

persona.nombre;       // "ANA" (getter)
persona.nombre = "Maria";  // setter
persona.info;       // "Maria, 30 anios"

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

Los getters (get) y setters (set) permiten controlar el acceso a propiedades. Se acceden como propiedades normales (sin paréntesis). Object.defineProperty() define atributos como writable y enumerable.

Map y Set
// Map: objeto con claves de cualquier tipo
const map = new Map();
map.set("nombre", "Ana");
map.set(42, "numero");
map.set({id: 1}, "objeto");

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

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

// Set: colección de valores únicos
const set = new Set([1, 2, 2, 3, 3]);
set.size;           // 3 (¡duplicados eliminados!)
set.add(4);
set.has(2);         // true
set.delete(1);

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

Map acepta claves de cualquier tipo (objetos, numeros) y mantiene el orden de inserción. Set guarda valores únicos. Usa [...new Set(arr)] para eliminar duplicados de un array.

Funções


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

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

// Un parámetro (paréntesis opcionales)
const doble = n => n * 2;

// Cuerpo con múltiples líneas
const calcular = (a, b) => {
  const resultado = a * b;
  return resultado + 1;
};

// Sin parámetros
const hola = () => "Hola!";

// Devolver un objeto (¡paréntesis!)
const crear = (nombre) => ({ nombre, activo: true });

// Diferencia: this
// Arrow NO tiene this propio (lo hereda del scope)
const obj = {
  nombre: "Ana",
  // INCORRECTO con arrow:
  // hablar: () => console.log(this.nombre),
  // CORRECTO con función normal:
  hablar() { console.log(this.nombre); }
};

Las arrow functions (=>) tienen sintaxis compacta y no tienen this propio — lo heredan del scope envolvente. Para devolver un objeto literal, envuélvelo en paréntesis: () => ({...}).

Métodos de Función
function saudar(saludo, nombre) {
  return `${saludo}, ${nombre}!`;
}

// call: invoca con this y args separados
saudar.call(null, "Hola", "Ana");
// "Hola, Ana!"

// apply: invoca con this y un array de args
saudar.apply(null, ["Hola", "Ana"]);
// "Hola, Ana!"

// bind: crea una nueva función con this fijo
const olaAna = saudar.bind(null, "Hola", "Ana");
olaAna();  // "Hola, Ana!"

// Uso práctico: fijar 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() y apply() invocan con this definido (difieren en los args: separados vs array). bind() crea una nueva función con this fijo — útil en event handlers.

Parámetros Default y Rest
// Default parameters
function saudar(nombre = "Mundo", puntuación = "!") {
  return `Hola, ${nombre}${puntuación}`;
}
saudar();          // "Hola, Mundo!"
saudar("Ana");     // "Hola, Ana!"
saudar("Ana", "."); // "Hola, Ana."

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

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

// Named parameters (vía destructuring)
function criarUser({ nombre, edad = 18, activo = true }) {
  return { nombre, edad, activo };
}
criarUser({ nombre: "Ana", edad: 25 });

Los parámetros default (= valor) se activan cuando el argumento es undefined. Rest (...args) recoge los argumentos en un array. Los named parameters vía destructuring son ideales para funciones con muchas opciones.

Recursión e IIFE
// Recursión: función que se llama a sí misma
function factorial(n) {
  if (n <= 1) return 1;  // caso base
  return n * factorial(n - 1);
}
factorial(5);  // 120

// Recursión con acumulador (tail)
function suma(n, acc = 0) {
  if (n === 0) return acc;
  return suma(n - 1, acc + n);
}
suma(100);  // 5050

// IIFE: Immediately Invoked Function Expression
(function() {
  const privado = "no accesible fora";
  console.log(privado);
})();

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

Recursión: una función que se llama a sí misma — siempre necesita un caso base. La IIFE se ejecuta inmediatamente y crea un scope privado. Útil para setup inicial y evitar contaminar el scope global.

Closures
// Closure: función que "recuerda" el scope donde fue creada
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 no es accesible directamente!

// Uso práctico: función factory
function multiplicador(fator) {
  return (n) => n * fator;
}
const doble = multiplicador(2);
const triple = multiplicador(3);
doble(5);   // 10
triple(5);  // 15

// Closure en loops (clásico)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 0, 1, 2 (let crea scope por iteración)

Una closure es una función que mantiene acceso al scope donde fue creada, incluso después de que ese scope termine. Permite datos privados y factories. Usa let en loops para crear scope por iteración.

Callbacks y Higher-Order
// Callback: función pasada como argumento
function procesar(arr, callback) {
  return arr.map(callback);
}
procesar([1,2,3], n => n * 2);  // [2,4,6]

// Higher-order: recibe/devuelve funciones
function aplicar(fn, valor) {
  return fn(valor);
}
aplicar(Math.sqrt, 16);  // 4

// Composición
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 aplazado)
function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

Una callback es una función pasada como argumento. Las higher-order functions reciben o devuelven funciones. pipe() compone funciones en secuencia. debounce() retrasa la ejecución hasta que dejen de llamarla.

Controlo de Fluxo


4 cards
if/else y switch
// if/else
if (edad >= 18) {
  console.log("Adulto");
} else if (edad >= 12) {
  console.log("Adolescente");
} else {
  console.log("Niño");
}

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

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

if/else para condiciones simples. switch para múltiples valores — ¡no olvides el break! switch(true) permite comparar expresiones como un if/else encadenado.

for, for...of y 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 (claves de objetos)
for (const key in { a: 1, b: 2 }) {
  console.log(key);  // "a", "b"
}

// for...of con í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 claves (objetos). Prefiere for...of con .entries() para obtener índice + valor en arrays.

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

// do...while (se ejecuta al menos 1 vez)
do {
  n--;
} while (n > 0);

// break y continue
for (let i = 0; i < 10; i++) {
  if (i === 3) continue;  // salta el 3
  if (i === 7) break;     // se detiene en el 7
  console.log(i);  // 0,1,2,4,5,6
}

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

while repite mientras la condición sea verdadera. do...while se ejecuta al menos una vez. break sale del loop, continue salta a la siguiente iteración. Las labels permiten salir de loops anidados.

Operadores Lógicos Avanzados
// Evaluación de cortocircuito
const nombre = input || "Anónimo";  // OR: 1er truthy
const config = user && user.config;  // AND: 1er falsy o último

// Nullish coalescing (??)
const val = 0 ?? "default";   // 0 (¡no es null!)
const val2 = null ?? "default";  // "default"

// Asignación lógica (ES2021)
let a = null;
a ??= "default";  // a = "default"

let b = 0;
b ||= 10;  // b = 10 (0 es falsy)
b ??= 20;  // b = 10 (0 no es null)

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

// Encadenamiento de comparaciones
// INCORRECTO: 1 < x < 10
// CORRECTO:
const inRange = x > 1 && x < 10;

|| devuelve el primer truthy, && el primer falsy o el último. ?? solo se activa con null/undefined. Operadores de asignación lógica: ??=, ||=, &&=.

Datas e Tempo


4 cards
Objeto Date
// Crear fechas
const ahora = new Date();
const data = new Date(2024, 0, 15);  // 15 Ene 2024
const data2 = new Date("2024-01-15");
const data3 = new Date(2024, 0, 15, 14, 30, 0);

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

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

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

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

getMonth() devuelve 0-11 (¡0 = Enero!). Date.now() da el timestamp en ms. Para comparar fechas, usa los operadores < y > directamente.

Formato de Fechas
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"
});
// "lunes, 15 de enero de 2024"

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

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

// Diferencia entre fechas (en días)
const d1 = new Date("2024-01-01");
const d2 = new Date("2024-01-15");
const diffDias = (d2 - d1) / (1000 * 60 * 60 * 24);
// 14

toLocaleDateString() formatea con un locale. Restar fechas da milisegundos — divide por 1000*60*60*24 para días. toISOString() devuelve el formato ISO 8601.

setTimeout y setInterval
// setTimeout: se ejecuta 1 vez tras un retraso
const timer = setTimeout(() => {
  console.log("Pasaron 2s");
}, 2000);

// Cancelar
clearTimeout(timer);

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

// Detener
clearInterval(interval);

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

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

setTimeout() se ejecuta una vez tras un retraso. setInterval() se repite. Ambos devuelven un ID para cancelar con clearTimeout()/clearInterval(). Usa requestAnimationFrame() para animaciones.

Intl.DateTimeFormat y Tiempo Relativo
// Intl.DateTimeFormat (reutilizable)
const fmt = new Intl.DateTimeFormat("pt-PT", {
  weekday: "long",
  day: "numeric",
  month: "long",
  year: "numeric"
});
fmt.format(new Date());
// "viernes, 15 de marzo de 2024"

// RelativeTimeFormat ("hace 3 días")
const rtf = new Intl.RelativeTimeFormat("pt", {
  numeric: "auto"
});
rtf.format(-3, "day");    // "há 3 días"
rtf.format(1, "month");   // "próximo mes"
rtf.format(-2, "hour");   // "hace 2 horas"

// Diferencia relativa
function tiempoRelativo(data) {
  const diff = data - Date.now();
  const días = Math.round(diff / 86400000);
  return rtf.format(días, "day");
}

Intl.DateTimeFormat es reutilizable y más rápido que toLocaleDateString() en bucles. Intl.RelativeTimeFormat crea textos como "hace 3 días" o "próximo mes". Soporta todos los locales.

DOM e Eventos


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

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

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

querySelector() acepta cualquier selector CSS y devuelve el primero. querySelectorAll() devuelve todos como un NodeList (tiene forEach). getElementsBy* devuelve un HTMLCollection (se actualiza en tiempo real).

Delegación de Eventos
// En vez de un listener por botón...
const lista = document.querySelector("#lista");

// Un listener en el padre (delegación)
lista.addEventListener("click", (e) => {
  const item = e.target.closest("li");
  if (!item) return;
  
  console.log("Clicó en:", item.textContent);
  
  // Ejemplo: eliminar item
  if (e.target.matches(".remover")) {
    item.remove();
  }
});

// Funciona para elementos futuros (dinámicos)
const novoLi = document.createElement("li");
novoLi.innerHTML = 'Nuevo <button class="remover">X</button>';
lista.appendChild(novoLi);  // ¡ya tiene listener!

Delegación: un listener en el padre captura eventos de los hijos vía bubbling. Usa e.target.closest() para encontrar el elemento deseado. Funciona para elementos añadidos dinámicamente sin registrar nuevos listeners.

MutationObserver
// Observar cambios en el DOM
const observer = new MutationObserver((mutations) => {
  mutations.forEach(m => {
    if (m.type === "childList") {
      console.log("Hijos cambiados:", m.addedNodes.length);
    }
    if (m.type === "attributes") {
      console.log(`Atributo ${m.attributeName} cambió`);
    }
  });
});

// Configurar qué observar
observer.observe(document.body, {
  childList: true,      // añadir/eliminar hijos
  attributes: true,     // cambios de atributos
  subtree: true,        // incluir descendientes
  characterData: true   // cambios de texto
});

// Dejar de observar
observer.disconnect();

MutationObserver detecta cambios en el DOM (hijos, atributos, texto). Reemplaza al antiguo MutationEvent (más eficiente). Útil para editores WYSIWYG, auto-guardado y detección de cambios.

Crear e Insertar Elementos
// Crear
const div = document.createElement("div");
div.textContent = "Hola";
div.classList.add("card");
div.id = "nuevo";

// Insertar
const pai = document.querySelector("#app");
pai.appendChild(div);          // último hijo
pai.prepend(div);              // primer hijo
pai.append(div, "texto");      // múltiples

// Insertar relativo a otro elemento
const ref = document.querySelector(".ref");
ref.before(div);   // antes de ref
ref.after(div);    // después de ref
ref.replaceWith(div);  // reemplaza ref

// Eliminar
div.remove();

createElement() crea el elemento, appendChild()/prepend() lo insertan en el padre. before()/after() insertan relativo a un elemento. remove() lo elimina del DOM.

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

// textContent: solo texto (seguro)
div.textContent = "<b>Negrita</b>";
// Muestra: <b>Negrita</b> (literal)

// innerHTML: parsea HTML (¡cuidado XSS!)
div.innerHTML = "<b>Negrita</b>";
// Muestra: Negrita (formateado)

// insertAdjacentHTML (no limpia los hijos)
div.insertAdjacentHTML("beforeend", "<p>Fin</p>");
div.insertAdjacentHTML("afterbegin", "<p>Inicio</p>");

// Rendimiento: 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 es seguro (no parsea HTML). innerHTML parsea pero es vulnerable a XSS. insertAdjacentHTML() inserta sin limpiar. DocumentFragment evita múltiples reflows.

Manipular Clases y Estilos
const el = document.querySelector(".card");

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

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

// Leer 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 gestiona clases sin sobrescribir. toggle() añade/elimina. style define estilos en línea. getComputedStyle() lee el CSS final. dataset accede a atributos data-*.

Formularios e Inputs
const form = document.querySelector("form");
const input = document.querySelector("#nombre");

// Leer valores
input.value;           // texto del input
input.checked;         // checkbox/radio
input.files;           // file input

// FormData (todos los campos)
const datos = new FormData(form);
datos.get("nombre");
datos.append("extra", "valor");

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

// Validación nativa
input.validity.valid;       // true/false
input.checkValidity();      // dispara invalid
input.setCustomValidity("Error!");

FormData recoge todos los campos del formulario. Úsalo con fetch() para enviar. e.preventDefault() impide el submit por defecto. input.validity da acceso a la validación nativa del navegador.

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

// Añadir un listener
btn.addEventListener("click", function(e) {
  console.log(e.target);       // elemento clicado
  console.log(e.type);         // "click"
  console.log(e.clientX);      // posición X
  e.preventDefault();          // impide la acción por defecto
  e.stopPropagation();         // impide el bubbling
});

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

// Opciones
btn.addEventListener("click", handler, {
  once: true,       // se elimina tras la 1ª ejecución
  capture: true,    // fase de captura
  passive: true     // no llama a preventDefault
});

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

addEventListener() registra un handler. e.target es el elemento que originó el evento. preventDefault() impide la acción por defecto. once: true se auto-elimina tras la primera ejecución.

IntersectionObserver (Lazy Load)
// Observa elementos que entran en el viewport
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add("visible");
      observer.unobserve(entry.target);
    }
  });
}, {
  threshold: 0.1,     // 10% visible
  rootMargin: "50px"  // margen extra
});

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

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

IntersectionObserver detecta cuándo los elementos entran en el viewport. Ideal para lazy loading, animaciones al hacer scroll y scroll infinito. threshold define el % visible para activar. unobserve() deja de observar.

Async e Promises


11 cards
Promises — Básico
// Crear una Promise
const promessa = new Promise((resolve, reject) => {
  const éxito = true;
  if (éxito) {
    resolve("Datos listos");
  } else {
    reject(new Error("Falló"));
  }
});

// Consumirla
promessa
  .then(resultado => console.log(resultado))
  .catch(error => console.error(error))
  .finally(() => console.log("Siempre se ejecuta"));

// Estados: pending → fulfilled | rejected
// Una Promise se resuelve solo 1 vez

Una Promise tiene 3 estados: pending, fulfilled, rejected. resolve() cumple, reject() rechaza. .then() maneja el éxito, .catch() el error, .finally() siempre se ejecuta.

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

// allSettled: espera a todas (incluso con error)
const resultados = await Promise.allSettled([
  fetch("/api/a"),
  fetch("/api/b")  // puede fallar
]);
resultados.forEach(r => {
  if (r.status === "fulfilled") console.log(r.value);
  else console.log(r.reason);
});

Promise.all() espera a todas y falla si una falla. Promise.allSettled() espera a todas sin fallar — devuelve el status de cada una. Usa all() cuando todas son obligatorias.

Web Workers (Básico)
// worker.js (archivo 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);  // envía datos

// Terminar
worker.terminate();

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

Web Workers ejecutan código en un hilo separado (no bloquea la UI). Comunícate vía postMessage()/onmessage. Ideal para cálculos pesados. terminate() finaliza el worker.

async/await
// Una función async devuelve una Promise
async function fetchData() {
  try {
    const res = await fetch("/api/datos");
    const datos = await res.json();
    return datos;
  } catch (error) {
    console.error("Error:", error);
    throw error;
  }
}

// Llamarla
fetchData()
  .then(datos => console.log(datos))
  .catch(e => console.error(e));

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

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

async marca la función como asíncrona (devuelve una Promise). await pausa hasta que la Promise se resuelve. Usa try/catch para errores. Solo puedes usar await dentro de funciones async (o top-level en módulos).

Promise.race y any
// race: la primera en resolver (o rechazar)
const maisRapida = await Promise.race([
  fetch("/api/servidor1"),
  fetch("/api/servidor2")
]);

// any: la primera con éxito (ignora errores)
const primera = await Promise.any([p1, p2, p3]);

// Timeout con 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() devuelve la primera en resolver O rechazar. Promise.any() devuelve la primera con éxito (ignora errores). Útil para timeouts y probar múltiples servidores.

Event Loop y 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");

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

El Event Loop procesa: 1) código síncrono, 2) microtasks (Promises, queueMicrotask), 3) macrotasks (setTimeout, eventos). Incluso con delay 0, setTimeout se ejecuta al final.

fetch — GET y Lectura
// GET simple
const res = await fetch("https://api.ejemplo.com/datos");
const datos = await res.json();

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

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

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

fetch() devuelve una Promise. res.json() parsea el body. Comprueba siempre res.ok (status 200-299) antes de usar los datos. res.status da el código HTTP.

Encadenamiento de Promises
// Encadenar .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 con async/await (más legible)
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();
}

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

Cada .then() devuelve una nueva Promise. Puedes devolver un valor u otra Promise. async/await es más legible para operaciones secuenciales. Los errores se propagan hasta .catch().

Iteradores Asíncronos (for await...of)
// Objeto con un iterador asíncrono
const paginas = {
  async *[Symbol.asyncIterator]() {
    let page = 1;
    while (true) {
      const res = await fetch(`/api?page=${page}`);
      const datos = await res.json();
      if (!datos.length) return;
      yield datos;
      page++;
    }
  }
};

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

// Stream de una respuesta 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 un generador asíncrono. Ideal para paginación, streams y lectura de archivos en chunks.

fetch — POST y Headers
// POST con JSON
const res = await fetch("/api/usuarios", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer token123"
  },
  body: JSON.stringify({ nombre: "Ana", edad: 30 })
});

const creado = await res.json();

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

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

Para POST, define Content-Type y usa JSON.stringify() en el body. Authorization envía tokens. Para subidas, usa FormData sin definir el header (el navegador lo define automáticamente).

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

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

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

try {
  const datos = await res.json();
  clearTimeout(timeout);
} catch (e) {
  if (e.name === "AbortError") {
    console.log("Petición cancelada");
  }
}

// Cancelar con un botón
const btn = document.querySelector("#cancelar");
btn.addEventListener("click", () => {
  controller.abort();
});

AbortController cancela peticiones fetch(). Pasa signal en las opciones. controller.abort() cancela. El error tiene name === "AbortError". Útil para timeouts y cancelar peticiones al navegar.

ES6+ Moderno


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

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

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

// Import default (cualquier nombre)
import Api from "./api.js";
import MiApi from "./api.js";  // mismo resultado

// Dynamic import (lazy loading)
const módulo = await import("./pesado.js");
módulo.funcion();

export exporta, import importa. Los Named exports permiten varios por archivo. Default export solo 1 por archivo. import() dinámico carga bajo demanda (code splitting). Usa .js en el path.

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

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

// WeakMap: claves débiles (el GC puede limpiar)
const cache = new WeakMap();
function procesar(obj) {
  if (cache.has(obj)) return cache.get(obj);
  const resultado = /* cálculo pesado */ obj;
  cache.set(obj, resultado);
  return resultado;
}

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

Symbol crea identificadores únicos. WeakMap/WeakSet tienen claves débiles — el Garbage Collector puede limpiar entradas si la clave se recolecta. Ideales para cachés y metadatos privados.

Clases
class Animal {
  // Campos privados (ES2022)
  #energia = 100;
  
  constructor(nombre, sonido) {
    this.nombre = nombre;
    this.sonido = sonido;
  }
  
  hablar() {
    return `${this.nombre} hace ${this.sonido}`;
  }
  
  // Getter/Setter
  get energia() { return this.#energia; }
  set energia(val) { this.#energia = Math.max(0, val); }
  
  // Método estático
  static crear(nombre) {
    return new Animal(nombre, "...");
  }
}

// Herencia
class Perro extends Animal {
  constructor(nombre) {
    super(nombre, "Au au");
  }
  
  hablar() {
    return `${super.hablar()}!`;
  }
}

class define clases. constructor() inicializa. #campo es privado. extends/super() para herencia. get/set crean accessors. static pertenece a la clase, no a la instancia.

Top-level Await y using
// Top-level await (ES2022, en módulos)
// ¡No necesita IIFE async!
const datos = await fetch("/api/config")
  .then(r => r.json());

// Import dinámico condicional
const módulo = process.env.DEBUG
  ? await import("./debug.js")
  : await import("./producción.js");

// using (ES2024 - resource management)
// using declara recursos que se limpian automáticamente
function abrirFicheiro() {
  return {
    [Symbol.dispose]() {
      console.log("Archivo cerrado");
    }
  };
}

{
  using fichero = abrirFicheiro();
  // usar fichero...
}  // Symbol.dispose() llamado automáticamente

Top-level await permite await fuera de funciones async (solo en módulos ES). using (ES2024) declara recursos con limpieza automática vía Symbol.dispose() — similar al using de C# o al with de Python.

Generadores e Iteradores
// 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 }

// Iterable con 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* crea un generador. yield pausa y devuelve un valor. .next() reanuda. Son lazy — solo calculan cuando se pide. Ideales para secuencias infinitas y pipelines de datos.

Proxy y Reflect
// Proxy: intercepta operaciones
const handler = {
  get(target, prop) {
    console.log(`Accedió: ${prop}`);
    return Reflect.get(target, prop);
  },
  set(target, prop, value) {
    if (typeof value !== "number") {
      throw new TypeError("¡Solo numeros!");
    }
    return Reflect.set(target, prop, value);
  }
};

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

// Validación reactiva
const state = new Proxy({ count: 0 }, {
  set(t, p, v) {
    Reflect.set(t, p, v);
    render();  // re-renderiza
    return true;
  }
});

Proxy intercepta operaciones (get, set, delete, etc.) en un objeto. Reflect proporciona los métodos estándar. Útil para validación, logging, reactividad y data binding. La base de frameworks como Vue.

Erros e Debug


6 cards
try/catch/finally
try {
  const datos = JSON.parse(texto);
  if (!datos.nombre) {
    throw new Error("Campo 'nombre' obligatorio");
  }
} catch (error) {
  console.error("Tipo:", error.name);     // "Error"
  console.error("Msg:", error.message);   // mensaje
  console.error("Stack:", error.stack);   // trace
} finally {
  console.log("Siempre se ejecuta");
  // Cerrar conexiones, limpiar recursos
}

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

try ejecuta código arriesgado. catch captura errores. finally siempre se ejecuta (limpieza). throw new Error() lanza errores personalizados. En async, usa try/catch en vez de .catch().

Manejo Global de Errores
// Errores no capturados
window.addEventListener("error", (e) => {
  console.error("Error global:", e.message);
  console.error("Archivo:", e.filename);
  console.error("Línea:", e.lineno);
  // Enviar a un servicio de logging
});

// Promises rechazadas sin catch
window.addEventListener("unhandledrejection", (e) => {
  console.error("Promise rechazada:", e.reason);
  e.preventDefault();  // no mostrar en consola
});

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

window.onerror captura errores no manejados. unhandledrejection captura Promises sin .catch(). Útil para logging global y reportar errores a servicios como Sentry. No sustituye al try/catch local.

Tipos de Errores
// TypeError: tipo incorrecto
null.toString();        // TypeError
"str"();               // TypeError

// ReferenceError: la variable no existe
console.log(x);        // ReferenceError

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

// RangeError: valor fuera del límite
new Array(-1);          // RangeError
(1).toFixed(101);       // RangeError

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

// Error 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 incorrecto), ReferenceError (no existe), SyntaxError (código inválido), RangeError (fuera del límite). Crea clases con extends Error para errores personalizados.

AggregateError y Error Cause
// Error cause (ES2022)
try {
  await fetch("/api");
} catch (e) {
  throw new Error("Fallo al cargar datos", { cause: e });
}
// error.cause contiene el error original

// AggregateError: agrupar múltiples errores
const errores = [
  new Error("Campo nombre obligatorio"),
  new Error("Email inválido"),
  new Error("Edad mínima: 18")
];

throw new AggregateError(errores, "Validación falló");

// Promise.any lanza AggregateError si todas fallan
try {
  await Promise.any([p1, p2, p3]);
} catch (e) {
  e.errors;  // array de todos los errores
}

Error cause ({ cause: e }) encadena errores manteniendo el original. AggregateError agrupa múltiples errores en uno solo. Promise.any() lanza AggregateError cuando todas las promises fallan.

Error Boundary (Patrón)
// Wrapper genérico para operaciones arriesgadas
function tentar(fn, fallback = null) {
  try {
    return { datos: fn(), error: null };
  } catch (e) {
    return { datos: fallback, error: e };
  }
}

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

if (error) {
  console.error("Parse falló:", error.message);
}

// Versión async
async function tentarAsync(fn, fallback = null) {
  try {
    return { datos: await fn(), error: null };
  } catch (e) {
    return { datos: fallback, error: e };
  }
}

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

Patrón try/catch encapsulado: devuelve { datos, error } en vez de lanzar. Evita try/catch repetitivo. Inspirado en el patrón Result de lenguajes funcionales. Versión async para Promises.

Debugging con DevTools
// debugger: pausa la ejecución en DevTools
function calcular(x) {
  debugger;  // breakpoint manual
  return x * 2;
}

// console.dir: ver la estructura del objeto
console.dir(document.body);

// Monitorear llamadas
const obj = { método() {} };
console.log(obj.método.toString());

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

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

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

debugger; crea un breakpoint en el código. performance.now() da timestamps precisos. performance.mark()/measure() miden secciones. Usa DevTools para breakpoints condicionales y watch expressions.

Truques e Dicas


9 cards
Desestructuración con Defaults
// Config con 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 opcionales con un objeto
function buscar({ query = "", page = 1, limit = 10 } = {}) {
  return `/api?q=${query}&p=${page}&l=${limit}`;
}

// Extraer con defaults
const { nombre = "Anónimo", role = "user" } = getUser();

Patrón de configuración: un objeto con defaults + = {} para permitir llamar sin argumentos. Más legible que los parámetros posicionales. Ideal para funciones con muchas opciones opcionales.

Encadenamiento e Inmutabilidad
// NO mutar — crear nuevo
const state = { items: [1, 2, 3], filtro: "todos" };

// Añadir un item
const nuevo = { ...state, items: [...state.items, 4] };

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

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

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

Inmutabilidad: crea nuevos objetos/arrays en vez de mutar. spread + map/filter para transformaciones. ES2023: toSorted(), toReversed(), toSpliced() no mutan el original.

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

// Crear y 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"

// Actualizar la URL sin recargar
const url = new URL(window.location);
url.searchParams.set("page", "2");
history.pushState({}, "", url);

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

URLSearchParams manipula query strings fácilmente. history.pushState() actualiza la URL sin recargar. new URL() parsea URLs completas. Esencial para filtros, paginación y compartir estado vía URL.

Operaciones Útiles en Arrays
// Último elemento
const último = arr.at(-1);

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

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

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

// Chunk (dividir en 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) accede al último elemento. [...new Set()] elimina duplicados. .flat(Infinity) aplana totalmente. Object.groupBy() agrupa por clave. chunk() divide en grupos de tamaño fijo.

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

// Retry con intentos
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 datos = await retry(() => fetch("/api").then(r => r.json()));

sleep() pausa código async sin bloquear el hilo. retry() repite operaciones que pueden fallar con un retraso creciente (backoff). Esencial para llamadas de red inestables.

Debounce y Throttle
// Debounce: espera X ms tras la última llamada
function debounce(fn, delay = 300) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

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

// Throttle: como máximo 1 vez cada X ms
function throttle(fn, limit = 200) {
  let último = 0;
  return (...args) => {
    const ahora = Date.now();
    if (ahora - último >= limit) {
      último = ahora;
      fn(...args);
    }
  };
}

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

Debounce espera X ms tras la última llamada (buscar mientras escribes). Throttle limita a 1 ejecución por X ms (scroll, resize). Ambos evitan llamadas excesivas a funciones pesadas.

Memoize y Cache
// Memoize (caché 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 con caché
const fib = memoize((n) => n <= 1 ? n : fib(n-1) + fib(n-2));
fib(40);  // ¡rápido!

// Caché simple con 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 en una caché — ideal para funciones puras con cálculos pesados. Usa Map como caché para peticiones repetidas. Solo funciona con funciones determinísticas (mismo input = mismo output).

Deep Clone y Comparación
// 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 (no copia funciones, 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 (referencias diferentes)

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

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

structuredClone() hace un deep clone nativo (soporta Date, Map, Set). JSON.parse(JSON.stringify()) es limitado (pierde funciones, Date se convierte en string). Los objetos se comparan por referencia, no por valor.

Regex — Esencial
// Probar y extraer
const email = "ana@ejemplo.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(
  /(?<anio>\d{4})-(?<mes>\d{2})-(?<dia>\d{2})/
);
groups.anio;  // "2024"

// Replace con regex
"10.50".replace(/\./g, ",");  // "10,50"
"hola mundo".replace(/\b\w/g, c => c.toUpperCase());
// "Hola Mundo"

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

.test() comprueba si coincide. .match() extrae grupos con (). (?<nombre>) crea named groups. .replace() con /g sustituye todas. \d = dígito, \w = palabra, + = 1 o más.