Primeros Pasos
Hola Mundo y Comentarios
JavaScript se ejecuta en navegadores y Node.js. console.log() es la salida de depuración principal. Los comentarios JSDoc (/** */) proporcionan información de tipo y documentación para los IDE. Usa 'use strict' o módulos ES para un análisis más seguro. Los comentarios se ignoran en tiempo de ejecución.
// Single-line comment
/* Multi-line
comment */
// Console output
console.log("Hello, World!");
console.warn("Warning message");
console.error("Error message");
console.table([{a: 1}, {a: 2}]);
// JSDoc comments (for documentation)
/**
* Adds two numbers
* @param {number} a - First number
* @param {number} b - Second number
* @returns {number} The sum
*/
function add(a, b) { return a + b; }Modo Estricto y Módulos
'use strict' habilita un análisis más estricto, capturando errores silenciosos. Los ES Modules (import/export) son el estándar moderno; CommonJS (require/module.exports) es el tradicional de Node.js. Usa siempre módulos para evitar contaminar el ámbito global. Los navegadores admiten <script type='module'>.
"use strict"; // enables strict mode (catches common mistakes)
x = 10; // ReferenceError: x is not defined (strict mode)
// ES Modules (modern)
// import { greet } from './utils.js';
// export function greet(name) { return `Hello, ${name}`; }
// CommonJS (Node.js traditional)
// const fs = require('fs');
// module.exports = { greet: (name) => `Hello, ${name}` };
// Running JS
// Browser: include in <script> or via dev server
// Node.js: node script.js
// Deno: deno run script.tsVariables: let, const, var
Prefiere siempre const; usa let solo cuando se necesite reasignar; evita var por completo. const previene la reasignación pero no la mutación: el contenido de objetos/arrays puede seguir cambiando. let/const tienen ámbito de bloque; var tiene ámbito de función y es hoisted (causando bugs). La TDZ impide usar variables antes de su declaración.
// const - cannot be reassigned (use by default)
const PI = 3.14159;
const user = { name: "Alice" };
user.name = "Bob"; // OK - object contents can change
// user = {}; // TypeError - can't reassign const
// let - block-scoped, can be reassigned
let count = 0;
count = 1;
if (true) {
let local = 5; // only accessible in this block
}
// var - function-scoped, hoisted (AVOID in modern code)
var old = "legacy"; // hoisted, can cause bugs
// Temporal Dead Zone (TDZ)
// console.log(x); // ReferenceError
// let x = 5;Tipos de Datos y typeof
JavaScript tiene 7 tipos primitivos (string, number, bigint, boolean, undefined, null, symbol) y tipos referencia (objetos, arrays, funciones). Los primitivos son inmutables y se copian por valor; los objetos son mutables y se pasan por referencia. typeof null devuelve 'object' por un bug histórico: usa === null para comprobar null.
// Primitive types (immutable, passed by value)
const str = "hello"; // string
const num = 42; // number (no separate int/float)
const big = 9007199254740993n; // bigint
const bool = true; // boolean
const undef = undefined; // undefined (no value)
const empty = null; // null (intentional empty)
const sym = Symbol("id"); // symbol (unique identifier)
// Reference types (mutable, passed by reference)
const arr = [1, 2, 3]; // object (array)
const obj = { a: 1 }; // object
const fn = function() {}; // object (function)
// typeof operator
console.log(typeof "hi"); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (historical bug!)
console.log(typeof []); // "object"
console.log(typeof {}); // "object"
console.log(typeof function(){}); // "function"Conversión y Coerción de Tipos
La coerción de tipos de JavaScript es notoriamente confusa. Usa siempre === (igualdad estricta) en lugar de == (floja) para evitar coerción inesperada. El operador + concatena si cualquiera de los operandos es una cadena; otros operadores coercionan a número. Valores falsos: false, 0, '', null, undefined, NaN. Usa Number.isNaN() para comprobar NaN (NaN !== NaN).
// Explicit conversion
String(42); // "42"
(42).toString(); // "42"
Number("42"); // 42
Number("3.14"); // 3.14
Number(""); // 0
Number("abc"); // NaN
parseInt("42px"); // 42
parseFloat("3.14abc"); // 3.14
Boolean(0); // false
Boolean(""); // false
Boolean("x"); // true
// Implicit coercion (often confusing)
console.log("5" + 3); // "53" (string concatenation)
console.log("5" - 3); // 2 (numeric subtraction)
console.log("5" * "2"); // 10
console.log(1 + "2" + 3); // "123"
console.log(true + 1); // 2
// Falsy values: false, 0, "", null, undefined, NaN
// Everything else is truthy
if ("0") console.log("truthy"); // runs! non-empty string
// Strict vs loose equality
console.log(1 == "1"); // true (loose, coerces)
console.log(1 === "1"); // false (strict, no coercion)
console.log(null == undefined); // true
console.log(null === undefined); // falseCadenas
Métodos de Cadena
Las cadenas son inmutables: los métodos devuelven nuevas cadenas. slice() admite índices negativos (desde el final); substring() no. replace() reemplaza solo la primera coincidencia; usa replaceAll() (ES2021) para todas. at() (ES2022) admite índices negativos. split() + join() es la forma idiomática de reemplazar caracteres en una cadena.
const s = "Hello, World";
// Length & access
console.log(s.length); // 12
console.log(s[0]); // "H"
console.log(s.charAt(0)); // "H"
console.log(s.at(-1)); // "d" (ES2022, negative index)
// Case
console.log(s.toUpperCase()); // "HELLO, WORLD"
console.log(s.toLowerCase()); // "hello, world"
// Search
console.log(s.indexOf("World")); // 7 (-1 if not found)
console.log(s.includes("World")); // true
console.log(s.startsWith("Hello")); // true
console.log(s.endsWith("World")); // true
// Extract
console.log(s.slice(0, 5)); // "Hello"
console.log(s.slice(-5)); // "World"
console.log(s.substring(0, 5)); // "Hello" (no negative)
console.log(s.split(", ")); // ["Hello", "World"]
// Modify
console.log(s.replace("o", "0")); // "Hell0, World"
console.log(s.replaceAll("o", "0")); // "Hell0, W0rld"
console.log(s.trim()); // remove whitespace
console.log(s.padStart(15, "*")); // "***Hello, World"
console.log("a,b,c".split(",").join("-")); // "a-b-c"Template Literals
Los template literals (backticks) permiten interpolación de cadenas con ${}, cadenas multilínea y plantillas etiquetadas. Son mucho más legibles que la concatenación de cadenas. Las plantillas etiquetadas permiten procesar template literals con una función: usadas por styled-components, graphql-tag, etc.
const name = "Alice";
const age = 30;
// Template literals (backticks)
const greeting = `Hello, ${name}! You are ${age} years old.`;
console.log(greeting);
// Multi-line strings
const html = `
<div>
<h1>${name}</h1>
<p>Age: ${age}</p>
</div>
`;
// Expressions inside ${}
console.log(`Next year: ${age + 1}`);
console.log(`Upper: ${name.toUpperCase()}`);
console.log(`${name.length} chars`);
// Tagged templates
function highlight(strings, ...values) {
return strings.reduce((acc, str, i) =>
acc + str + (values[i] ? `<b>${values[i]}</b>` : ''), '');
}
const result = highlight`Name: ${name}, Age: ${age}`;
// "Name: <b>Alice</b>, Age: <b>30</b>"Búsqueda en Cadenas y Regex
Las cadenas de JavaScript admiten regex vía match(), matchAll(), replace(), search() y split(). Los grupos de captura con nombre (?<name>...) (ES2018) hacen el regex más legible. matchAll() devuelve un iterador (más eficiente que match() para regex global). Usa .test() para comprobar si un patrón coincide sin extraer.
const text = "The quick brown fox jumps over the lazy dog";
// Search methods
console.log(text.search(/brown/)); // 10 (index of match)
console.log(text.match(/\w+/g)); // ["The","quick","brown",...]
console.log(text.matchAll(/\w+/g)); // iterator of matches
// Replace with regex
console.log(text.replace(/o/g, "0")); // replace all 'o'
console.log(text.replace(/(\w+)/g, "\$1!")); // capture group
// Capture groups
const date = "2024-01-15";
const [, year, month, day] = date.match(/(\d{4})-(\d{2})-(\d{2})/);
console.log(year, month, day); // 2024 01 15
// Named capture groups (ES2018)
const m = date.match(/(?<year>\d{4})-(?<month>\d{2})/);
console.log(m.groups.year); // 2024
console.log(m.groups.month); // 01
// Test if pattern matches
const emailRe = /^[^@]+@[^@]+\.[^@]+$/;
console.log(emailRe.test("[email protected]")); // trueIteración y Spread de Cadenas
Las cadenas son iterables con for...of. El operador spread [...] divide una cadena en caracteres: esencial para el manejo correcto de emojis (pares sustitutos). La comparación de cadenas es lexicográfica por unidad de código UTF-16, así que usa localeCompare() para ordenación consciente del locale. Los emojis y algunos caracteres tienen 2 unidades de código.
const s = "Hello";
// Iterate characters
for (const char of s) {
console.log(char); // H, e, l, l, o
}
// Spread into array
const chars = [...s]; // ["H", "e", "l", "l", "o"]
console.log(chars);
// Spread with map/filter
const upper = [...s].map(c => c.toUpperCase()).join("");
console.log(upper); // HELLO
// String comparison
console.log("a" < "b"); // true (lexicographic)
console.log("apple" < "banana"); // true
console.log("Z" < "a"); // true (uppercase < lowercase in ASCII)
// Locale-aware comparison
console.log("ö".localeCompare("o", "de")); // locale-specific
// Repeat
console.log("ab".repeat(3)); // "ababab"
// Code points (handles emoji correctly)
const emoji = "😀";
console.log(emoji.length); // 2 (surrogate pair!)
console.log([...emoji].length); // 1 (correct)
console.log("😀".codePointAt(0)); // 128512Números y Matemáticas
Números y Operadores
JavaScript tiene un único tipo number (float de 64 bits): no hay int/float separado. BigInt (sufijo n) maneja enteros más allá de 2^53. La aritmética de coma flotante tiene problemas de precisión (0.1 + 0.2 !== 0.3): usa Number.EPSILON para comparaciones. ** es el operador de exponentación (ES2016).
// JavaScript has one number type (IEEE 754 double)
const int = 42;
const float = 3.14;
const exp = 1e6; // 1000000
const hex = 0xff; // 255
const bin = 0b1010; // 10
const oct = 0o755; // 493
const big = 9007199254740993n; // BigInt (arbitrary precision)
// Arithmetic
console.log(10 / 3); // 3.333...
console.log(10 % 3); // 1 (remainder)
console.log(2 ** 10); // 1024 (exponent)
console.log(Math.floor(10 / 3)); // 3
console.log(Math.trunc(-3.7)); // -3 (toward zero)
// Increment/decrement
let x = 5;
console.log(x++); // 5 (post-increment, returns old)
console.log(++x); // 7 (pre-increment, returns new)
// Floating point issues
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false!
// Fix: use Number.EPSILON or round
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // trueObjeto Math
El objeto Math proporciona constantes y funciones. Todas las funciones trigonométricas usan radianes. Math.random() devuelve [0, 1): multiplica y trunca para rangos enteros. Para aleatoriedad criptográfica, usa crypto.getRandomValues(). Math.max/min no aceptan arrays directamente: espárcelos con ....
// Constants
console.log(Math.PI); // 3.141592653589793
console.log(Math.E); // 2.718281828459045
console.log(Math.SQRT2); // 1.4142135623730951
// Rounding
console.log(Math.round(3.7)); // 4 (nearest)
console.log(Math.floor(3.7)); // 3 (down)
console.log(Math.ceil(3.2)); // 4 (up)
console.log(Math.trunc(-3.7)); // -3 (toward zero)
console.log(Math.sign(-5)); // -1 (sign: -1, 0, or 1)
// Power & roots
console.log(Math.pow(2, 10)); // 1024
console.log(Math.sqrt(144)); // 12
console.log(Math.cbrt(27)); // 3
console.log(Math.abs(-5)); // 5
// Min/Max
console.log(Math.max(1, 5, 3)); // 5
console.log(Math.min(1, 5, 3)); // 1
console.log(Math.max(...[1, 5, 3])); // 5 (spread array)
// Trigonometry (radians)
console.log(Math.sin(Math.PI / 2)); // 1
console.log(Math.cos(0)); // 1
console.log(Math.PI / 180 * 90); // radians from degrees
// Random
console.log(Math.random()); // 0 to <1
console.log(Math.floor(Math.random() * 100)); // 0-99 integerMétodos de Number y Parsing
Especifica siempre la raíz (base) para parseInt(): los navegadores antiguos interpretan los ceros iniciales como octal. Number.isNaN() es fiable; el global isNaN() coercion (isNaN('abc') es true). toFixed() devuelve una cadena, no un número. Los números más allá de MAX_SAFE_INTEGER pierden precisión: usa BigInt.
// Number methods
const num = 1234.5678;
console.log(num.toFixed(2)); // "1234.57" (string)
console.log(num.toPrecision(3)); // "1.23e+3"
console.log(num.toString(2)); // binary string
console.log((255).toString(16)); // "ff" (hex)
// Number object methods
console.log(Number.isInteger(42)); // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isNaN(NaN)); // true (reliable)
console.log(Number.isNaN("NaN")); // false (global isNaN would be true)
console.log(Number.parseInt("42px")); // 42
console.log(Number.parseFloat("3.14")); // 3.14
// Parsing strings
console.log(parseInt("42", 10)); // 42 (always specify radix!)
console.log(parseInt("0xff", 16)); // 255
console.log(parseFloat("3.14abc")); // 3.14
// Safe integers
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.isSafeInteger(2 ** 53)); // false
// NaN checks
const result = Number("abc"); // NaN
console.log(Number.isNaN(result)); // true
console.log(result === NaN); // false! NaN !== NaNEstructuras de Datos
Arrays
Los arrays son dinámicos, ordenados y pueden contener tipos mixtos. push/pop son O(1); shift/unshift/splice son O(n). find()/findIndex() toman una función predicado. forEach() no devuelve nada: usa map() para transformar. Los arrays son objetos: typeof [] es 'object'. Usa Array.isArray() para comprobar.
// Creating arrays
const nums = [1, 2, 3, 4, 5];
const mixed = [1, "hello", true, null];
const empty = new Array(5); // [empty x 5]
// Access & modify
console.log(nums[0]); // 1
console.log(nums.length); // 5
nums[0] = 0; // modify
console.log(nums.at(-1)); // 5 (negative index, ES2022)
// Add/remove
nums.push(6); // add to end, returns new length
nums.pop(); // remove from end, returns element
nums.unshift(0); // add to start
nums.shift(); // remove from start
nums.splice(1, 2); // remove 2 elements at index 1
nums.splice(1, 0, "a"); // insert at index 1
// Search
console.log(nums.indexOf(3)); // index or -1
console.log(nums.includes(3)); // true/false
console.log(nums.find(n => n > 3)); // first match
console.log(nums.findIndex(n => n > 3)); // index of first match
// Iterate
nums.forEach((val, idx) => console.log(idx, val));
for (const [idx, val] of nums.entries()) {
console.log(idx, val);
}Métodos de Array (map, filter, reduce)
map/filter/reduce son la trinidad sagrada de la programación funcional de arrays. map transforma, filter selecciona, reduce agrega. Son encadenables y no mutan el original (excepto reverse/sort). sort() convierte a cadenas por defecto: proporciona siempre un comparador para números. flat() aplana arrays anidados.
const nums = [1, 2, 3, 4, 5];
// map - transform each element (returns new array)
const doubled = nums.map(n => n * 2); // [2, 4, 6, 8, 10]
const withIndex = nums.map((n, i) => `${i}:${n}`);
// filter - keep elements that pass test (returns new array)
const evens = nums.filter(n => n % 2 === 0); // [2, 4]
// reduce - accumulate to single value
const sum = nums.reduce((acc, n) => acc + n, 0); // 15
const product = nums.reduce((acc, n) => acc * n, 1); // 120
const max = nums.reduce((a, b) => Math.max(a, b));
// Chaining
const result = nums
.filter(n => n % 2 === 0) // [2, 4]
.map(n => n * 10) // [20, 40]
.reduce((sum, n) => sum + n, 0); // 60
// Other useful methods
console.log(nums.slice(1, 3)); // [2, 3] (copy portion)
console.log(nums.concat([6, 7])); // [1,2,3,4,5,6,7]
console.log([...nums, 6, 7]); // spread (modern)
console.log(nums.reverse()); // reverse in-place
console.log(nums.sort((a, b) => a - b)); // numeric sort
console.log([3, 1, 2].sort()); // [1, 2, 3] (string sort!)
console.log(nums.flat()); // flatten one level
console.log([1, [2, [3]]].flat(Infinity)); // [1, 2, 3]Objetos
Los objetos son colecciones clave-valor (las claves son strings o symbols). Notación con punto para claves estáticas, notación con corchetes para claves dinámicas/especiales. Los nombres de propiedad computados {[expr]: val} son ES6. Object.keys/values/entries extraen arrays; Object.fromEntries invierte entries. for...in itera claves (incluidas las heredadas).
// Object literal
const user = {
name: "Alice",
age: 30,
"is-admin": false, // keys with special chars need quotes
greet() { // method shorthand
return `Hello, I'm ${this.name}`;
}
};
// Access
console.log(user.name); // dot notation
console.log(user["is-admin"]); // bracket notation (for dynamic/special keys)
// Computed property names (ES6)
const key = "dynamic";
const obj = { [key]: "value", [`id_${1}`]: 100 };
// Add/modify/delete
user.email = "[email protected]"; // add
user.age = 31; // modify
delete user["is-admin"]; // delete
// Check property existence
console.log("name" in user); // true
console.log(user.hasOwnProperty("name")); // true
// Iterate
for (const key in user) {
console.log(key, user[key]);
}
// Object methods
console.log(Object.keys(user)); // ["name", "age", ...]
console.log(Object.values(user)); // ["Alice", 31, ...]
console.log(Object.entries(user)); // [["name","Alice"], ...]
console.log(Object.fromEntries([["a", 1]])); // {a: 1}Destructuring y Spread
El destructuring extrae valores de objetos/arrays de forma concisa. Admite renombrado (key: newName), defaults (= value) y rest (...rest). El spread (...) expande iterables/objetos: genial para fusionar y copia superficial. El spread de objetos sobrescribe claves duplicadas (gana el último). El destructuring en parámetros de función es potente para config opcional.
// Object destructuring
const user = { name: "Alice", age: 30, email: "[email protected]" };
const { name, age } = user; // extract by key
const { name: fullName, email = "N/A" } = user; // rename + default
const { ...rest } = user; // rest pattern
// name -> undefined, fullName -> "Alice", rest -> {age, email}
// Array destructuring
const [a, b, c] = [1, 2, 3];
const [first, , third] = [1, 2, 3]; // skip elements
const [head, ...tail] = [1, 2, 3, 4]; // head=1, tail=[2,3,4]
// Swap variables
let x = 1, y = 2;
[x, y] = [y, x]; // x=2, y=1
// Nested destructuring
const { data: { users } } = response;
const [[a, b], [c, d]] = [[1, 2], [3, 4]];
// Function parameters
function greet({ name, greeting = "Hello" }) {
return `${greeting}, ${name}`;
}
greet({ name: "Alice" }); // "Hello, Alice"
// Spread operator
const arr1 = [1, 2], arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]
const obj1 = { a: 1 }, obj2 = { b: 2 };
const combined = { ...obj1, ...obj2 }; // {a:1, b:2}
const clone = { ...user }; // shallow copyMap, Set, WeakMap
Map admite cualquier tipo de clave (objetos, no solo strings) y mantiene el orden de inserción, a diferencia de los objetos planos. Set almacena valores únicos: perfecto para desduplicación. Las claves de WeakMap/WeakSet son referencias débiles (pueden ser garbage collected), previniendo fugas de memoria. Usa Map cuando necesites claves no string o add/delete frecuentes.
// Map - key-value pairs, any key type, maintains insertion order
const map = new Map();
map.set("name", "Alice");
map.set(42, "number key");
map.set({ obj: true }, "object key");
console.log(map.get("name")); // "Alice"
console.log(map.has("name")); // true
console.log(map.size); // 3
map.delete(42);
map.clear();
// Iterate Map
for (const [key, value] of map) {
console.log(key, value);
}
// Set - unique values
const set = new Set([1, 2, 3, 2, 1]);
console.log(set.size); // 3 (duplicates removed)
set.add(4);
set.has(2); // true
set.delete(1);
// Set operations
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]); // {1,2,3,4}
const intersection = new Set([...a].filter(x => b.has(x))); // {2,3}
const difference = new Set([...a].filter(x => !b.has(x))); // {1}
// WeakMap/WeakSet - keys must be objects, GC-friendly
const weakMap = new WeakMap();
weakMap.set({}, "value"); // key can be garbage collectedFlujo de Control
If / Else y Ternario
Usa if/else para lógica compleja; ternario para selección simple de valor. && y || short-circuit (útiles para defaults/condicionales). ?? (nullish coalescing) solo comprueba null/undefined, a diferencia de || que comprueba todos los valores falsos. ?. (optional chaining) accede de forma segura a propiedades anidadas sin errores.
const score = 85;
// if / else if / else
if (score >= 90) {
console.log("A");
} else if (score >= 80) {
console.log("B");
} else {
console.log("C");
}
// Ternary operator (expression)
const grade = score >= 60 ? "pass" : "fail";
// Nested ternary (avoid - hard to read)
const status = score >= 90 ? "excellent"
: score >= 80 ? "good"
: score >= 60 ? "pass"
: "fail";
// Short-circuit evaluation
const name = user?.name || "Anonymous"; // default value
const value = condition && doSomething(); // execute if true
// Nullish coalescing (??) - only null/undefined, not falsy
const count = 0 ?? 10; // 0 (not 10, because 0 is not null/undefined)
const name2 = null ?? "default"; // "default"
// Optional chaining (?.)
const city = user?.address?.city; // undefined if any link is null
const length = user?.name?.length; // undefined if user or name is nullSentencia Switch
switch compara con igualdad estricta (===), así que el tipo importa. No olvides break: sin él, la ejecución cae al siguiente case. Agrupa cases apilándolos (case 6: case 7:). Switch es más limpio que largas cadenas if/else para valores discretos. El código moderno a veces prefiere tablas de búsqueda en objetos.
const day = 3;
// Traditional switch (use break!)
switch (day) {
case 1:
console.log("Monday");
break; // without break, falls through!
case 2:
console.log("Tuesday");
break;
case 6:
case 7: // multiple cases share code
console.log("Weekend");
break;
default:
console.log("Weekday");
}
// Switch with return (no break needed)
function getColor(type) {
switch (type) {
case "success": return "green";
case "error": return "red";
case "warning": return "yellow";
default: return "gray";
}
}
// Strict equality (===)
switch (1) {
case "1": console.log("string"); // NOT matched
case 1: console.log("number"); // matched
}Bucles
Usa for...of para arrays/cadenas (valores), for...in para objetos (claves): nunca for...in en arrays (itera índices como strings e incluye el prototipo). while comprueba antes de ejecutar; do...while se ejecuta al menos una vez. break sale, continue salta. Usa .entries() para obtener índice+valor con for...of.
// for loop
for (let i = 0; i < 5; i++) {
console.log(i);
}
// for...of (iterable values - arrays, strings, maps)
const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
console.log(fruit);
}
// for...in (object keys - AVOID for arrays!)
const user = { name: "Alice", age: 30 };
for (const key in user) {
console.log(key, user[key]);
}
// while
let count = 0;
while (count < 3) {
console.log(count);
count++;
}
// do...while (runs at least once)
let i = 0;
do {
console.log(i);
i++;
} while (i < 3);
// break & continue
for (let i = 0; i < 10; i++) {
if (i === 5) break; // exit loop
if (i % 2 === 0) continue; // skip iteration
console.log(i);
}
// Iterate with index
for (const [index, value] of fruits.entries()) {
console.log(index, value);
}Iteradores y Generadores
Los iteradores implementan next() devolviendo {value, done}. Los generadores (function*) simplifican la creación de iteradores con yield: pausan la ejecución y se reanudan en next(). Los generadores son perezosos (calculan bajo demanda) y pueden ser infinitos. Úsalos para iterables personalizados, secuencias y flujos async.
// Iterable protocol (Symbol.iterator)
const range = {
from: 1, to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { done: true };
}
};
}
};
for (const num of range) console.log(num); // 1, 2, 3, 4, 5
console.log([...range]); // [1, 2, 3, 4, 5]
// Generator function (function*)
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const gen = idGenerator();
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next().value); // 3
// Generator with yield*
function* nested() {
yield 1;
yield* [2, 3, 4]; // delegate to another iterable
yield 5;
}Funciones
Declaraciones y Expresiones de Función
Las declaraciones de función son hoisted (pueden llamarse antes de la definición); las expresiones no. Las arrow functions son concisas y no tienen su propio 'this' (lo heredan del ámbito envolvente). Las IIFEs crean ámbitos privados (menos necesarias con módulos). Las funciones son de primera clase: pásalas como argumentos, devuélvelas, almacénalas en variables.
// Function declaration (hoisted - can be called before definition)
greet("Alice"); // works (hoisted)
function greet(name) {
return `Hello, ${name}!`;
}
// Function expression (not hoisted)
const greet2 = function(name) {
return `Hi, ${name}!`;
};
// Named function expression (for recursion/stack traces)
const factorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1);
};
// Arrow function (ES6) - concise, no own this
const add = (a, b) => a + b;
const square = x => x * x;
const greet3 = () => "Hello!";
const log = x => { console.log(x); }; // block body needs return
// IIFE (Immediately Invoked Function Expression)
const result = (function() {
const private = "secret";
return private.toUpperCase();
})();
// Functions are first-class (can be passed/returned)
function apply(fn, value) {
return fn(value);
}
console.log(apply(square, 5)); // 25Arrow Functions y this
Las arrow functions no tienen su propio 'this', 'arguments', 'super' o 'new.target': los heredan del ámbito envolvente. Esto las hace perfectas para callbacks (especialmente en métodos de clase). Pero no pueden usarse como constructores ni métodos que necesiten su propio 'this'. Usa funciones regulares para métodos de objeto.
// Arrow function variations
const add = (a, b) => a + b; // implicit return
const greet = name => `Hi ${name}`; // single param, no parens
const log = () => console.log("hi"); // no params
const obj = (x, y) => ({ x, y }); // return object needs parens
const multi = (a, b) => { // block body
const sum = a + b;
return sum * 2;
};
// Arrow functions don't have their own 'this'
function Timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // 'this' refers to Timer instance
console.log(this.seconds);
}, 1000);
}
// Regular function has its own 'this'
function Counter() {
this.count = 0;
// Regular function: 'this' is undefined (strict) or global
document.addEventListener("click", function() {
// this.count++; // ERROR: this is not Counter
});
// Arrow function: 'this' is Counter
document.addEventListener("click", () => {
this.count++; // works!
});
}
// Arrow functions can't be constructors
// const obj = new arrowFunc(); // TypeErrorClosures
Las closures son funciones que 'recuerdan' las variables de su ámbito de definición, incluso después de que ese ámbito termine. Habilitan privacidad de datos (patrón módulo), memoización, currying y aplicación parcial. Cada función en JavaScript es una closure. La función interna mantiene una referencia a las variables externas, no una copia.
// A closure is a function that remembers its outer variables
function makeCounter() {
let count = 0; // private variable
return function() {
return ++count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count is private - can't access directly
// Module pattern (pre-ES6)
const calculator = (function() {
const result = 0; // private
return {
add(x) { return result + x; },
multiply(x) { return result * x; }
};
})();
// Practical: memoization
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (!(key in cache)) {
cache[key] = fn.apply(this, args);
}
return cache[key];
};
}
const slowFib = memoize(n =>
n < 2 ? n : slowFib(n - 1) + slowFib(n - 2)
);
// Currying with closures
const multiply = a => b => a * b;
const double = multiply(2);
console.log(double(5)); // 10Arguments y Rest/Spread
Los parámetros por defecto proporcionan valores de respaldo. Los rest parameters (...name) recogen argumentos extra en un array real: prefiérelos sobre el objeto legacy 'arguments'. El spread (...) expande un array en argumentos individuales. El destructuring en parámetros habilita objetos de config con nombre y opcionales: un patrón común de API.
// Default parameters
function greet(name = "Guest", greeting = "Hello") {
return `${greeting}, ${name}!`;
}
greet(); // "Hello, Guest!"
greet("Alice"); // "Hello, Alice!"
greet("Bob", "Hi"); // "Hi, Bob!"
// Rest parameters (...args collects into array)
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
function log(tag, ...args) {
console.log(tag, args);
}
// Spread (opposite of rest)
const nums = [1, 2, 3];
console.log(Math.max(...nums)); // 3 (spread array as args)
console.log(sum(...nums)); // 6
// arguments object (old way, avoid in modern code)
function old() {
console.log(arguments); // array-like, not a real array
const args = Array.from(arguments); // convert
}
// Destructuring parameters
function process({ name, age = 0 } = {}) {
console.log(name, age);
}
process({ name: "Alice" }); // Alice 0
process(); // undefined 0 (default empty object)POO y Clases
Clases y Constructor
Las clases ES6 son azúcar sintáctico sobre prototipos. Los campos privados (#name) son ES2022 y verdaderamente privados (a diferencia de la convención _name). Los getters/setters permiten propiedades calculadas. Los miembros estáticos pertenecen a la clase, no a las instancias. Los class fields (name = value) inicializan propiedades de instancia sin constructor.
class Person {
// Fields (class fields proposal - ES2022)
species = "human"; // instance field
// Private fields (ES2022)
#ssn = "secret"; // truly private
// Static field/method
static count = 0;
// Constructor
constructor(name, age) {
this.name = name;
this.age = age;
Person.count++;
}
// Instance method
greet() {
return `Hello, I'm ${this.name}`;
}
// Getter
get info() {
return `${this.name}, ${this.age}`;
}
// Setter
set age(value) {
if (value < 0) throw new Error("Invalid age");
this._age = value;
}
get age() {
return this._age;
}
// Static method
static create(name) {
return new Person(name, 0);
}
}
const alice = new Person("Alice", 30);
console.log(alice.greet()); // Hello, I'm Alice
console.log(alice.info); // Alice, 30
console.log(Person.count); // 1
// alice.#ssn; // SyntaxError - private!Herencia y Polimorfismo
extends crea herencia; super() llama al constructor padre (requerido antes de usar 'this'). Sobrescribe métodos redefiniéndolos. JavaScript es de herencia simple, pero los mixins (fábricas de clases) proporcionan composición. instanceof comprueba la cadena de prototipos. El polimorfismo funciona mediante la sobrescritura de métodos.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
// Static method can be inherited
static create(type, name) {
return new type(name);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // must call super() first
this.breed = breed;
}
speak() {
return `${this.name} barks`; // override
}
fetch() {
return `${this.name} fetches`;
}
}
class Cat extends Animal {
speak() {
return `${this.name} meows`;
}
}
// Polymorphism
const animals = [new Dog("Rex", "Lab"), new Cat("Whiskers")];
animals.forEach(a => console.log(a.speak()));
// Rex barks
// Whiskers meows
// instanceof checks
const dog = new Dog("Buddy", "Poodle");
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog.constructor.name); // "Dog"
// Mixins (multiple inheritance alternative)
const Walker = (Base) => class extends Base {
walk() { return `${this.name} walks`; }
};
class Robot extends Walker(Animal) {}Prototipos
JavaScript usa herencia prototípica: los objetos heredan de otros objetos vía una cadena de prototipos. __proto__ está deprecado; usa Object.getPrototypeOf/setPrototypeOf. Las clases son azúcar sintáctico sobre este sistema. Modificar prototipos integrados (Array.prototype) es peligroso: puede romper código. Prefiere composición sobre herencia profunda.
// Every object has a prototype (chain of inheritance)
const obj = {};
console.log(obj.__proto__); // Object.prototype
console.log(Object.getPrototypeOf(obj)); // preferred
// Constructor function (pre-class syntax)
function OldPerson(name) {
this.name = name;
}
OldPerson.prototype.greet = function() {
return `Hi, ${this.name}`;
};
const p = new OldPerson("Alice");
console.log(p.greet()); // Hi, Alice
// Prototype chain
// p -> OldPerson.prototype -> Object.prototype -> null
// Adding to prototype (affects all instances)
Array.prototype.last = function() {
return this[this.length - 1];
};
console.log([1, 2, 3].last()); // 3
// Object.create (prototypal inheritance)
const animal = { type: "unknown" };
const dog = Object.create(animal);
dog.type = "dog";
console.log(dog.type); // "dog" (own property)
// Check own vs inherited
console.log(dog.hasOwnProperty("type")); // true
console.log("type" in dog); // true (includes inherited)
// Get prototype chain
let proto = Object.getPrototypeOf(dog);
while (proto) {
console.log(proto);
proto = Object.getPrototypeOf(proto);
}Manejo de Errores
Try / Catch / Finally
try/catch/finally maneja excepciones. catch vincula el objeto de error (que tiene .message y .stack). Usa instanceof para manejar tipos de error específicos de forma diferente. Relanza siempre los errores desconocidos tras manejar los esperados. Crea errores personalizados extendiendo Error para manejo de errores específico de la aplicación.
try {
const result = JSON.parse('{"invalid json"');
} catch (error) {
console.error("Parse error:", error.message);
console.error("Stack:", error.stack);
} finally {
console.log("Always runs");
}
// Catch specific error types
try {
const data = JSON.parse(input);
if (!data.name) throw new TypeError("name is required");
} catch (error) {
if (error instanceof SyntaxError) {
console.log("JSON syntax error");
} else if (error instanceof TypeError) {
console.log("Type error:", error.message);
} else {
throw error; // re-throw unknown errors
}
}
// Error properties
const err = new Error("Something went wrong");
err.code = "CUSTOM_ERROR";
err.statusCode = 500;
throw err;
// Built-in error types
// Error, TypeError, RangeError, ReferenceError
// SyntaxError, URIError, EvalErrorErrores Personalizados
Extiende Error para crear tipos de error personalizados con contexto extra (campos, códigos). Establece siempre this.name para que coincida con el nombre de la clase. Usa instanceof para capturar tipos de error específicos. El encadenamiento de errores (ES2022 { cause }) preserva el error original para depuración. Una buena jerarquía de errores hace el manejo preciso.
// Custom error class
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
class DatabaseError extends Error {
constructor(message, query) {
super(message);
this.name = "DatabaseError";
this.query = query;
}
}
// Usage
function validateUser(user) {
if (!user.email) {
throw new ValidationError("email", "Email is required");
}
if (!user.email.includes("@")) {
throw new ValidationError("email", "Invalid email format");
}
}
// Handling custom errors
try {
validateUser({ name: "Alice" });
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation failed: ${error.field} - ${error.message}`);
} else if (error instanceof DatabaseError) {
console.log(`DB error in query: ${error.query}`);
} else {
console.log("Unexpected error:", error);
}
}
// Error chaining (ES2022)
try {
throw new Error("Original cause");
} catch (cause) {
throw new Error("Failed to process", { cause });
}Async y Promesas
Callbacks
Los callbacks son funciones pasadas para ser llamadas más tarde. La convención error-first (err, data) es estándar en Node.js. Los callbacks anidados crean 'callback hell': código profundamente anidado y difícil de leer. Las Promesas y async/await resuelven esto. setTimeout/setInterval son APIs comunes basados en callbacks.
// Callback pattern (old way)
function fetchData(url, callback) {
setTimeout(() => {
callback(null, { data: "result" });
}, 1000);
}
// Callback with error-first convention (Node.js style)
fetchData("/api", (error, data) => {
if (error) {
console.error(error);
return;
}
console.log(data);
});
// Callback hell (pyramid of doom)
fetchUser(userId, (err, user) => {
if (err) return handleError(err);
fetchPosts(user.id, (err, posts) => {
if (err) return handleError(err);
fetchComments(posts[0].id, (err, comments) => {
if (err) return handleError(err);
// deeply nested...
});
});
});
// Event listeners are callbacks
button.addEventListener("click", (event) => {
console.log("Clicked!", event.target);
});
// setTimeout / setInterval
setTimeout(() => console.log("After 1s"), 1000);
const id = setInterval(() => console.log("tick"), 1000);
clearInterval(id); // stopPromesas
Las Promesas representan valores futuros con tres estados: pending, fulfilled, rejected. .then() maneja el éxito, .catch() maneja errores, .finally() siempre se ejecuta. Promise.all() espera a todas (falla rápido); allSettled() espera a todas (nunca falla); race() devuelve la primera en settled; any() devuelve la primera con éxito.
// Creating a promise
const promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("Data received");
} else {
reject(new Error("Failed"));
}
});
// Consuming a promise
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log("Done"));
// Chaining
fetch("/api/users")
.then(response => response.json())
.then(users => users.filter(u => u.active))
.then(active => console.log(active))
.catch(err => console.error("Error:", err));
// Promise.all - wait for all (fails if any fails)
Promise.all([
fetch("/api/users").then(r => r.json()),
fetch("/api/posts").then(r => r.json())
]).then(([users, posts]) => {
console.log(users, posts);
});
// Promise.allSettled - wait for all (never fails)
Promise.allSettled([p1, p2]).then(results => {
results.forEach(r => {
if (r.status === "fulfilled") console.log(r.value);
else console.log(r.reason);
});
});
// Promise.race - first to settle wins
Promise.race([p1, p2]).then(first => console.log(first));
// Promise.any - first to succeed
Promise.any([p1, p2]).then(first => console.log(first));Async / Await
async/await es azúcar sintáctico sobre promesas: hace que el código async parezca síncrono. 'await' pausa la función hasta que la promesa se settle. Envuelve siempre await en try/catch para manejo de errores. Usa Promise.all() para operaciones paralelas (más rápido que await secuencial en un bucle). Top-level await funciona en módulos ES.
// async function always returns a Promise
async function fetchData() {
// await pauses until the promise resolves
const response = await fetch("/api/users");
const data = await response.json();
return data;
}
// Error handling with try/catch
async function getUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return await res.json();
} catch (error) {
console.error("Failed:", error);
return null;
}
}
// Sequential vs parallel
async function sequential() {
const a = await fetch("/api/a"); // waits for a
const b = await fetch("/api/b"); // then waits for b
return [a, b];
}
async function parallel() {
const [a, b] = await Promise.all([ // both at once
fetch("/api/a"),
fetch("/api/b")
]);
return [a, b];
}
// Top-level await (ES2022, in modules)
// const data = await fetch("/api").then(r => r.json());
// Iterating with async
async function processUrls(urls) {
for (const url of urls) {
const data = await fetch(url);
console.log(data);
}
}
// Promise.all with map (parallel)
async function fetchAll(urls) {
return Promise.all(urls.map(url => fetch(url)));
}Manipulación del DOM
Seleccionar y Modificar Elementos
querySelector/querySelectorAll (selectores CSS) son la forma moderna de seleccionar elementos. textContent es más seguro que innerHTML (previene XSS). classList proporciona add/remove/toggle/contains para clases. dataset accede a atributos data-*. Sanea siempre la entrada del usuario antes de establecer innerHTML.
// Selecting elements
const el = document.querySelector("#app"); // first match
const all = document.querySelectorAll(".item"); // all matches (NodeList)
const byId = document.getElementById("app");
const byClass = document.getElementsByClassName("item"); // HTMLCollection
// Modifying content
el.textContent = "Hello"; // text only (safe from XSS)
el.innerHTML = "<b>Bold</b>"; // HTML (XSS risk with user input!)
el.innerText = "Visible text"; // respects CSS visibility
// Attributes
el.setAttribute("data-id", "123");
const id = el.getAttribute("data-id");
el.removeAttribute("disabled");
el.dataset.id; // access data-* attributes
el.id = "new-id";
el.className = "active highlighted";
el.classList.add("active");
el.classList.remove("old");
el.classList.toggle("hidden");
el.classList.contains("active");
// Styles
el.style.color = "red";
el.style.backgroundColor = "blue"; // camelCase
el.style.cssText = "color: red; font-size: 16px;";
// Creating elements
const div = document.createElement("div");
div.textContent = "New element";
div.classList.add("box");
document.body.appendChild(div);
document.body.prepend(div); // add to beginning
document.body.insertBefore(div, referenceEl);Eventos
addEventListener es preferido sobre las propiedades on<event> (admite múltiples listeners). La delegación de eventos (escuchar en un padre) es eficiente para elementos añadidos dinámicamente. e.target es lo que se clicó; e.currentTarget es el elemento con el listener. preventDefault() detiene el comportamiento por defecto; stopPropagation() detiene el bubbling.
// Add event listener
button.addEventListener("click", (event) => {
console.log("Clicked!", event.target);
console.log("Current target:", event.currentTarget);
});
// Common events
// click, dblclick, mousedown, mouseup, mousemove
// keydown, keyup, keypress
// submit, change, input, focus, blur
// load, DOMContentLoaded, resize, scroll
// Event object properties
input.addEventListener("keydown", (e) => {
console.log(e.key); // "Enter", "a", etc.
console.log(e.code); // "KeyA", "Enter"
console.log(e.ctrlKey); // true if Ctrl held
e.preventDefault(); // stop default behavior
e.stopPropagation(); // stop bubbling
});
// Event delegation (efficient for many elements)
document.addEventListener("click", (e) => {
if (e.target.matches(".delete-btn")) {
const id = e.target.dataset.id;
deleteItem(id);
}
});
// Custom events
const customEvent = new CustomEvent("userLogin", {
detail: { userId: 123 }
});
element.dispatchEvent(customEvent);
element.addEventListener("userLogin", (e) => {
console.log("User logged in:", e.detail.userId);
});
// Remove listener (must be same function reference)
const handler = () => console.log("click");
button.addEventListener("click", handler);
button.removeEventListener("click", handler);Módulos y JSON
ES Modules
Los ES Modules (import/export) son el estándar moderno, soportados en navegadores y Node.js. Default export (uno por módulo) vs named exports (múltiples). Dynamic import() habilita lazy loading. Los módulos están siempre en modo estricto y tienen su propio ámbito. Usa type='module' en las etiquetas script de HTML.
// math.js - exporting
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Calculator { /* ... */ }
// Default export (one per module)
export default function greet(name) {
return `Hello, ${name}`;
}
// main.js - importing
import greet from "./math.js"; // default
import { add, PI } from "./math.js"; // named
import * as math from "./math.js"; // namespace
import { add as plus } from "./math.js"; // rename
import greet, { add } from "./math.js"; // default + named
// Dynamic import (returns Promise)
const module = await import("./math.js");
console.log(module.add(2, 3));
// Conditional/dynamic loading
if (featureEnabled) {
const { default: Feature } = await import("./feature.js");
new Feature();
}
// Re-export
export { add } from "./math.js";
export * from "./utils.js";
// In HTML
// <script type="module" src="app.js"></script>
// Modules are deferred and in strict mode by defaultJSON
JSON.stringify() convierte valores JS a cadenas JSON; JSON.parse() lo invierte. Usa replacers/revivers para filtrar o transformar durante la conversión. JSON no admite funciones, undefined, Dates (se convierten en strings) ni referencias circulares. fetch().json() analiza respuestas JSON automáticamente. Envuelve siempre JSON.parse en try/catch para entrada no confiable.
// JSON is the standard data interchange format
// JSON supports: string, number, boolean, null, array, object
// JavaScript object to JSON string
const user = { name: "Alice", age: 30, active: true };
const jsonStr = JSON.stringify(user);
// '{"name":"Alice","age":30,"active":true}'
// Pretty print
const pretty = JSON.stringify(user, null, 2);
// {
// "name": "Alice",
// "age": 30,
// "active": true
// }
// JSON string to JavaScript object
const parsed = JSON.parse('{"name":"Bob","age":25}');
console.log(parsed.name); // "Bob"
// Replacer function (filter/transform during stringify)
const filtered = JSON.stringify(user, (key, value) => {
if (key === "age") return undefined; // exclude
return value;
});
// Reviver function (transform during parse)
const data = JSON.parse(jsonStr, (key, value) => {
if (key === "date") return new Date(value);
return value;
});
// Fetch JSON from API
async function getUsers() {
const res = await fetch("/api/users");
return res.json(); // parses JSON automatically
}
// JSON limitations
// - No functions, undefined, or dates
// - No circular references (throws error)
// - Keys must be strings (with quotes)Fecha y Hora
Date de JavaScript es notoriamente incómodo: los meses son 0-indexed (Enero = 0), los días son 1-indexed. Los objetos Date son mutables. toISOString() da UTC; toLocaleString() da hora local. Para trabajo serio con fechas, usa una librería como date-fns o dayjs. Intl.DateTimeFormat proporciona formateo consciente del locale.
// Create dates
const now = new Date();
const specific = new Date("2024-01-15T10:30:00");
const fromMs = new Date(1705315200000);
const fromParts = new Date(2024, 0, 15, 10, 30); // month is 0-indexed!
// Get components
console.log(now.getFullYear()); // 2024
console.log(now.getMonth()); // 0-11 (January = 0!)
console.log(now.getDate()); // 1-31
console.log(now.getDay()); // 0-6 (Sunday = 0)
console.log(now.getHours()); // 0-23
console.log(now.getTime()); // milliseconds since epoch
// Set components
now.setFullYear(2025);
now.setMonth(11); // December
// Formatting
console.log(now.toISOString()); // "2024-01-15T10:30:00.000Z"
console.log(now.toLocaleDateString()); // "1/15/2024" (locale)
console.log(now.toLocaleString()); // "1/15/2024, 10:30:00 AM"
// Timestamps
const start = Date.now(); // milliseconds since epoch
// ... do work ...
console.log(`Took ${Date.now() - start}ms`);
// Intl for formatting (modern)
const formatter = new Intl.DateTimeFormat("zh-CN", {
year: "numeric", month: "long", day: "numeric"
});
console.log(formatter.format(now)); // "2024年1月15日"Características de ES6+
let, const y Ámbito de Bloque
Prefiere const por defecto, let cuando se necesite reasignar, y evita var por completo. const previene la reasignación pero los objetos/arrays siguen siendo mutables. let y const tienen ámbito de bloque y viven en la Temporal Dead Zone antes de la declaración (a diferencia de var que es hoisted como undefined). Esto previene muchos bugs sutiles.
// var is function-scoped (hoisted, leaks out of blocks)
// let and const are block-scoped (stay inside {})
{
var x = 1; // accessible outside the block
let y = 2; // block-scoped
const z = 3; // block-scoped, cannot reassign
}
console.log(x); // 1
// console.log(y); // ReferenceError
// const prevents reassignment, NOT mutation
const arr = [1, 2, 3];
arr.push(4); // OK — mutating the array
// arr = [5]; // TypeError — reassigning const
// Temporal Dead Zone: let/const can't be used before declaration
// console.log(a); // ReferenceError (not undefined like var)
let a = 10;Arrow Functions y this
Las arrow functions son concisas y heredan 'this' del ámbito envolvente: perfectas para callbacks y métodos que necesitan el 'this' externo. Pero no pueden usarse como constructores y no tienen objeto 'arguments'. No uses arrow functions para métodos de objeto si necesitas que 'this' se refiera al objeto (usa métodos regulares en su lugar).
// Arrow functions: concise syntax, lexically bound 'this'
const add = (a, b) => a + b;
const square = x => x * x; // single param, no parens
const greet = name => `Hello, ${name}`; // template literal
const noop = () => {}; // no params, empty body
// Returning an object literal needs parens
const makeUser = (name, age) => ({ name, age });
// Arrow functions DON'T have their own 'this' — they inherit it
function Counter() {
this.count = 0;
setInterval(() => {
this.count++; // 'this' is the Counter instance (lexical)
console.log(this.count);
}, 1000);
}
// Regular function would lose 'this' (it'd be window/undefined)Asignación por Destructuring
El destructuring extrae valores de objetos/arrays a variables en una línea: más limpio que el acceso manual a propiedades. El destructuring de objetos usa { key }, el de arrays usa [index]. Admite renombrado (key: alias), defaults (key = default), rest (...rest) y patrones anidados. Muy usado en props de React y parámetros de función.
// Object destructuring
const user = { name: "Alice", age: 30, city: "NYC" };
const { name, age } = user;
console.log(name, age); // Alice 30
// Rename and default values
const { name: fullName, country = "USA" } = user;
console.log(fullName, country); // Alice USA
// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first, second, rest); // 1 2 [3,4,5]
// Swap variables (no temp needed!)
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
// Destructuring in function parameters
function greet({ name, greeting = "Hello" }) {
console.log(`${greeting}, ${name}`);
}
greet({ name: "Bob" }); // Hello, Bob
// Nested destructuring
const { data: { results } } = response;Operadores Spread y Rest
El operador ... es 'spread' al expandir (en arrays/objetos/llamadas) y 'rest' al recoger (en params/destructuring). Spread crea copias superficiales y fusiona objetos (las claves posteriores sobrescriben las anteriores). Los rest params reemplazan el viejo objeto 'arguments' y son Arrays reales. Ambos son esenciales en JS moderno.
// Spread (...) expands iterables into individual elements
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5] (copy + append)
const merged = [...arr1, ...arr2];
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 } (shallow copy)
const updated = { ...obj1, b: 99 }; // override b: { a: 1, b: 99 }
// Rest (...) collects multiple elements into an array
function sum(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10
// Rest in destructuring
const [head, ...tail] = [1, 2, 3, 4];
console.log(head, tail); // 1 [2, 3, 4]
// Spread in function calls
Math.max(...[1, 5, 3]); // 5 (same as Math.max(1, 5, 3))Template Literals y Plantillas Etiquetadas
Los template literals (backticks) admiten cadenas multilínea e interpolación ${}: mucho más limpio que la concatenación. Las plantillas etiquetadas permiten a una función procesar las partes literales y los valores interpolados, habilitando formateo personalizado, saneamiento (por ejemplo, escapar HTML) o i18n. Populares en styled-components y graphql-tag.
// Template literals: backticks, multi-line, interpolation
const name = "Alice";
const msg = `Hello ${name},
this spans
multiple lines`;
// Expressions inside ${}
const price = 19.99;
const tax = 0.08;
console.log(`Total: ${(price * (1 + tax)).toFixed(2)}`); // Total: 21.59
// Nested template literals
const items = ["apple", "banana"];
const html = `
<ul>
${items.map(i => `<li>${i}</li>`).join("")}
</ul>
`;
// Tagged templates: function processes the literal
function highlight(strings, ...values) {
return strings.reduce((acc, str, i) =>
acc + str + (values[i] ? `**${values[i]}**` : ""), "");
}
const result = highlight`Name: ${name}, Age: ${30}`;
// "Name: **Alice**, Age: **30**"Optional Chaining y Nullish Coalescing
Optional chaining (?.) short-circuit a undefined si cualquier parte de la cadena es null/undefined: elimina comprobaciones manuales verbosas. Nullish coalescing (??) proporciona defaults SOLO para null/undefined, a diferencia de || que también sobrescribe 0, '' y false. Juntos manejan los patrones más comunes de 'datos faltantes' de forma segura. Disponible desde ES2020.
// Optional chaining (?.): safely access nested properties
const user = { profile: { name: "Alice" } };
console.log(user?.profile?.name); // "Alice"
console.log(user?.settings?.theme); // undefined (no error!)
console.log(user?.profile?.address?.city); // undefined
// Without ?., you'd write:
// user && user.profile && user.profile.name
// Optional method calls
const result = obj?.method?.();
const firstChar = str?.[0];
// Nullish coalescing (??): default only for null/undefined
const theme = user.settings?.theme ?? "light"; // "light"
const count = 0 ?? 10; // 0 (NOT 10 — ?? keeps falsy but valid values)
const name = "" ?? "Anonymous"; // "" (empty string is kept!)
// vs || which treats all falsy values (0, "", false) as missing
const count2 = 0 || 10; // 10 (probably not what you want!)Eventos y Manejo de Eventos
Fundamentos de addEventListener
addEventListener es la forma moderna de vincular eventos: admite múltiples handlers, soporta delegación de eventos y ofrece opciones como once, passive y capture. Guarda siempre una referencia al handler si necesitas eliminarlo después (las funciones anónimas no pueden eliminarse). El objeto event lleva target, currentTarget, preventDefault() y stopPropagation().
// Modern event binding (preferred over onclick=)
const button = document.querySelector("#myBtn");
button.addEventListener("click", function(event) {
console.log("Clicked!", event.target);
event.preventDefault(); // stop default action (e.g., form submit)
});
// Multiple listeners can be attached
button.addEventListener("click", handler1);
button.addEventListener("click", handler2);
// Remove a specific listener (must be same reference)
button.removeEventListener("click", handler1);
// Common event types
// "click", "dblclick", "mousedown", "mouseup", "mousemove"
// "keydown", "keyup", "keypress"
// "submit", "change", "input", "focus", "blur"
// "load", "DOMContentLoaded", "resize", "scroll"
// Once option: auto-remove after first trigger
button.addEventListener("click", handler, { once: true });Delegación de Eventos
La delegación de eventos vincula un listener a un padre que maneja eventos de todos los hijos vía event bubbling. Usa event.target.matches(selector) para filtrar. Es mucho más eficiente que vincular a cada hijo y maneja automáticamente elementos añadidos dinámicamente. El trade-off: el padre debe ser un ancestro común que siempre exista.
// Delegate events to a parent instead of each child
// Efficient for dynamically added elements
const list = document.querySelector("#item-list");
list.addEventListener("click", (event) => {
// event.target is the actual element clicked
if (event.target.matches("li.item")) {
console.log("Clicked:", event.target.textContent);
event.target.classList.toggle("selected");
}
});
// Now dynamically added items automatically work
list.insertAdjacentHTML("beforeend", "<li class='item'>New Item</li>");
// Benefits:
// 1. One listener instead of many (memory efficient)
// 2. Works for elements added after binding
// 3. No need to re-bind when DOM changesPropagación de Eventos (bubbling y capturing)
Los eventos se propagan en tres fases: capturing (top-down), target y bubbling (bottom-up, por defecto). La mayoría de handlers se ejecutan en la fase bubbling. stopPropagation() impide que el evento llegue a elementos padre; stopImmediatePropagation() también detiene otros handlers en el mismo elemento. Usa capturing (tercer arg true) para handlers que deben ejecutarse antes que los handlers hijos.
<div id="outer">
<div id="inner">
<button id="btn">Click</button>
</div>
</div>
// Events flow in three phases:
// 1. Capturing: top -> target (window -> document -> ... -> target)
// 2. Target: at the target element
// 3. Bubbling: target -> top (default phase most handlers run in)
// Bubbling (default): child fires first, then parents
btn.addEventListener("click", () => console.log("button"));
inner.addEventListener("click", () => console.log("inner"));
outer.addEventListener("click", () => console.log("outer"));
// Click button logs: button -> inner -> outer
// stopPropagation: prevent bubbling to parents
btn.addEventListener("click", (e) => {
e.stopPropagation();
console.log("only button");
});
// Capturing phase (third arg = true)
outer.addEventListener("click", handler, true); // runs during capture
// stopImmediatePropagation: stop other handlers on SAME element tooEventos Personalizados
CustomEvent permite crear eventos específicos de aplicación con datos de payload en la propiedad 'detail'. Combinado con dispatchEvent, esto habilita un patrón pub/sub para desacoplar componentes: los módulos se comunican sin referencias directas. Usa una convención de nombres como 'namespace:action' para evitar colisiones. Es la base de muchos frameworks de elementos personalizados.
// Create and dispatch custom events
const event = new CustomEvent("userLoggedIn", {
detail: { userId: 42, name: "Alice" },
bubbles: true, // allow bubbling
});
document.dispatchEvent(event);
// Listen for the custom event
document.addEventListener("userLoggedIn", (e) => {
console.log("User logged in:", e.detail.userId, e.detail.name);
});
// Practical: decoupled communication between components
class Cart {
constructor() {
this.items = [];
}
add(item) {
this.items.push(item);
window.dispatchEvent(new CustomEvent("cart:updated", {
detail: { count: this.items.length }
}));
}
}
// Any module can listen without knowing about Cart internals
window.addEventListener("cart:updated", (e) => {
updateBadge(e.detail.count);
});Eventos de Teclado y Formulario
Los eventos de teclado dan e.key (tecla lógica como 'a', 'Enter') y e.code (tecla física como 'KeyA'). Usa e.key para la mayoría de la lógica. El submit de formulario siempre necesita preventDefault() para evitar la recarga de página. FormData + Object.fromEntries recopila fácilmente datos de formulario. 'input' se dispara continuamente; 'change' se dispara cuando el campo pierde el foco: elige según cuándo quieras la validación.
// Keyboard events
document.addEventListener("keydown", (e) => {
console.log(e.key, e.code); // e.key="a", e.code="KeyA"
if (e.key === "Escape") closeModal();
if (e.ctrlKey && e.key === "s") { e.preventDefault(); save(); }
});
// Form events
const form = document.querySelector("#myForm");
form.addEventListener("submit", (e) => {
e.preventDefault(); // stop page reload
const formData = new FormData(form);
const data = Object.fromEntries(formData);
console.log(data); // { username: "...", email: "..." }
});
// Input validation on change/blur
const emailInput = document.querySelector("#email");
emailInput.addEventListener("blur", () => {
if (!emailInput.value.includes("@")) {
emailInput.setCustomValidity("Enter a valid email");
} else {
emailInput.setCustomValidity("");
}
});
// Change vs input:
// 'input' fires on every keystroke; 'change' fires on blurFetch API y AJAX
fetch Básico (GET)
fetch() es el reemplazo moderno de XMLHttpRequest: basado en promesas y más limpio. Crucialmente, fetch solo rechaza en errores de red, NO en estados HTTP de error (404, 500). Comprueba siempre response.ok (status 200-299) antes de analizar. Usa async/await para código secuencial legible. response.json() es async porque lee el stream del body.
// fetch() returns a Promise; .json() returns another Promise
fetch("https://api.example.com/users")
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json(); // or .text(), .blob(), .arrayBuffer()
})
.then(data => console.log(data))
.catch(error => console.error("Fetch failed:", error));
// fetch does NOT reject on HTTP errors (404, 500) — only on network
// failures. You MUST check response.ok manually.
// Same with async/await (preferred)
async function getUsers() {
try {
const res = await fetch("https://api.example.com/users");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
console.error(err);
}
}POST, PUT, DELETE con fetch
El objeto de opciones de fetch configura method, headers y body. Para JSON, establece Content-Type: application/json y JSON.stringify el body. Para subida de archivos, usa FormData (no establezcas Content-Type manualmente: el navegador añade el boundary multipart). PUT reemplaza un recurso entero; PATCH lo actualiza parcialmente.
// POST: create a resource
async function createUser(data) {
const res = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
return res.json();
}
createUser({ name: "Alice", email: "[email protected]" });
// PUT: update a resource (full replace)
await fetch("/api/users/1", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Alice Updated" }),
});
// DELETE
await fetch("/api/users/1", { method: "DELETE" });
// Sending form data (file uploads)
const formData = new FormData();
formData.append("file", fileInput.files[0]);
await fetch("/upload", { method: "POST", body: formData });
// Don't set Content-Type for FormData — browser sets it with boundaryCabeceras de Request y Auth
Las cabeceras transportan metadatos y tokens de auth. Los Bearer tokens (JWT) van en la cabecera Authorization. Algunas cabeceras están 'prohibidas' (controladas por el navegador) como Host y Cookie. CORS lo impone el navegador, no el servidor: no puedes evitarlo desde JS del cliente; el servidor debe enviar Access-Control-Allow-Origin. Las peticiones preflight OPTIONS ocurren para peticiones no simples.
// Custom headers (e.g., Bearer token auth)
async function fetchWithAuth(url, options = {}) {
const token = localStorage.getItem("authToken");
const res = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`,
"Accept": "application/json",
...options.headers, // allow overrides
},
});
return res;
}
// Common headers:
// Content-Type: application/json | application/x-www-form-urlencoded
// Authorization: Bearer <token> | Basic <base64>
// Accept: application/json
// X-Requested-With: XMLHttpRequest (anti-CSRF)
// CORS: browser blocks cross-origin requests unless the server
// returns Access-Control-Allow-Origin. fetch can't bypass CORS.AbortController (Cancelar Peticiones)
AbortController cancela peticiones fetch: esencial para search-as-you-type, navegación fuera o timeouts. Pasa signal a fetch; llamar a controller.abort() dispara un AbortError. Sin esto, las peticiones obsoletas pueden actualizar la UI desordenada. AbortController también funciona con otras APIs async y es el mecanismo de cancelación estándar en JS moderno.
// AbortController lets you cancel in-flight fetch requests
const controller = new AbortController();
async function fetchWithTimeout(url, ms = 5000) {
const timeout = setTimeout(() => controller.abort(), ms);
try {
const res = await fetch(url, { signal: controller.signal });
return await res.json();
} catch (err) {
if (err.name === "AbortError") {
console.log("Request was aborted (timeout or cancel)");
} else {
throw err;
}
} finally {
clearTimeout(timeout);
}
}
// Cancel manually (e.g., user navigated away)
// controller.abort();
// Practical: cancel on new search input
searchInput.addEventListener("input", (e) => {
controller.abort(); // cancel previous request
fetchResults(e.target.value);
});Respuestas en Streaming
response.body es un ReadableStream: puedes procesar datos en chunks a medida que llegan en lugar de bufferizar toda la respuesta en memoria. Esto es esencial para archivos grandes, logs en streaming o datos en tiempo real. Usa TextDecoder para streams de texto. El bucle reader.read() continúa hasta que done es true. El streaming evita picos de memoria en payloads grandes.
// Read a large response in chunks (streaming)
async function streamJson(url) {
const res = await fetch(url);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Process complete lines
const lines = buffer.split("\n");
buffer = lines.pop(); // keep incomplete line
for (const line of lines) {
if (line.trim()) console.log(JSON.parse(line));
}
}
}
// Useful for: large files, NDJSON logs, real-time data feeds
// Processes data as it arrives instead of waiting for the full responseWeb Storage (LocalStorage y SessionStorage)
Fundamentos de localStorage y sessionStorage
localStorage persiste indefinidamente; sessionStorage se limpia cuando se cierra la pestaña. Ambos almacenan solo strings: usa JSON.stringify/parse para objetos. El almacenamiento es síncrono y bloquea el hilo principal, así que evita almacenar datos grandes. Disponible en todos los navegadores modernos pero puede estar deshabilitado en modo de navegación privada. La capacidad es ~5-10MB por origen.
// Both store strings only — JSON.stringify objects
// localStorage: persists until cleared (survives browser restart)
// sessionStorage: cleared when the tab closes
// localStorage API
localStorage.setItem("username", "Alice");
const name = localStorage.getItem("username"); // "Alice"
localStorage.removeItem("username");
localStorage.clear(); // remove ALL items
// sessionStorage API (same interface, tab-scoped)
sessionStorage.setItem("tempToken", "abc123");
// Storing objects (must serialize)
const prefs = { theme: "dark", lang: "en" };
localStorage.setItem("prefs", JSON.stringify(prefs));
const loaded = JSON.parse(localStorage.getItem("prefs"));
console.log(loaded.theme); // "dark"
// Check if storage exists (private mode may disable it)
if (typeof Storage !== "undefined") {
// localStorage/sessionStorage available
}Helper de Storage con TTL y JSON
Web Storage no tiene expiración integrada: este wrapper añade TTL (time-to-live) almacenando una marca de tiempo de expiración junto al valor. Es el patrón estándar para cachear respuestas de API o datos de sesión que deben expirar. Envuelve siempre el acceso a storage en try/catch en producción, ya que JSON.parse puede lanzar excepción con datos corruptos y la cuota puede excederse.
// A robust storage wrapper with expiration (TTL)
const store = {
set(key, value, ttlMs) {
const item = {
value: value,
expiry: ttlMs ? Date.now() + ttlMs : null,
};
localStorage.setItem(key, JSON.stringify(item));
},
get(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const item = JSON.parse(raw);
if (item.expiry && Date.now() > item.expiry) {
localStorage.removeItem(key);
return null; // expired
}
return item.value;
},
remove(key) { localStorage.removeItem(key); },
};
// Usage: cache data for 5 minutes
store.set("userData", { name: "Alice" }, 5 * 60 * 1000);
console.log(store.get("userData")); // { name: "Alice" }
// After 5 min: returns null and cleans upEventos de Storage (Sincronización entre Pestañas)
El evento storage es un canal de comunicación integrado entre pestañas: cuando una pestaña modifica localStorage, todas las demás pestañas del mismo origen reciben el evento (pero no la pestaña originaria). Esto habilita sincronizar estado como login/logout, actualizaciones de carrito o cambios de tema entre pestañas sin WebSockets. El evento incluye key, oldValue, newValue y url.
// The 'storage' event fires in OTHER tabs when storage changes
// (not in the tab that made the change)
window.addEventListener("storage", (event) => {
console.log("Key changed:", event.key);
console.log("Old value:", event.oldValue);
console.log("New value:", event.newValue);
console.log("URL:", event.url);
if (event.key === "cart") {
updateCartDisplay(JSON.parse(event.newValue));
}
});
// Practical: sync logout across tabs
// Tab A: localStorage.setItem("logout", Date.now());
// Tab B: receives storage event -> redirects to login
// Practical: broadcast a message to all tabs
function broadcast(type, data) {
localStorage.setItem("broadcast", JSON.stringify({ type, data, t: Date.now() }));
}
window.addEventListener("storage", (e) => {
if (e.key === "broadcast") {
const msg = JSON.parse(e.newValue);
handleMessage(msg);
}
});IndexedDB (Almacenamiento Estructurado Grande)
IndexedDB es una potente base de datos NoSQL en el navegador: asíncrona, transaccional y capaz de almacenar muchos más datos que localStorage (cientos de MB). Admite índices, cursores y transacciones. La API raw es basada en callbacks y verbosa; el paquete npm 'idb' proporciona un wrapper limpio basado en Promesas. Usa IndexedDB para apps offline-first, cachés grandes o datos complejos del lado del cliente.
// IndexedDB: async, transactional NoSQL store for large data
// Capacity: hundreds of MB to GB (far more than localStorage's 5MB)
// Open a database
const request = indexedDB.open("MyDatabase", 1);
request.onupgradeneeded = (e) => {
const db = e.target.result;
// Create an object store (like a table) with a key path
if (!db.objectStoreNames.contains("users")) {
db.createObjectStore("users", { keyPath: "id" });
}
};
request.onsuccess = (e) => {
const db = e.target.result;
// Add data in a transaction
const tx = db.transaction("users", "readwrite");
const store = tx.objectStore("users");
store.add({ id: 1, name: "Alice", age: 30 });
store.add({ id: 2, name: "Bob", age: 25 });
// Query
store.get(1).onsuccess = (e) => {
console.log(e.target.result); // { id: 1, name: "Alice", age: 30 }
};
};
// For easier use, consider the 'idb' library (Promise-based wrapper)Comparación Cookies vs Storage
Las cookies se envían con cada petición HTTP (añadiendo ancho de banda) y son el estándar para auth del lado del servidor (IDs de sesión, tokens CSRF). localStorage/sessionStorage son solo del cliente y almacenan muchos más datos. IndexedDB es para datos estructurados grandes. Elige según: ¿lo necesita el servidor? (cookie) ¿Cuántos datos? (storage vs IndexedDB) ¿Cuánto tiempo? (session vs local). Establece Secure, HttpOnly y SameSite en cookies sensibles a la seguridad.
// COOKIES: sent with every HTTP request, ~4KB limit
document.cookie = "session=abc123; max-age=3600; path=/; Secure; SameSite=Strict";
console.log(document.cookie); // "session=abc123; theme=dark"
// Set cookie with attributes
document.cookie = "token=xyz; max-age=86400; Secure; HttpOnly; SameSite=Lax";
// Note: HttpOnly can't be set via JS (server-only for security)
// LOCAL STORAGE: ~5-10MB, NOT sent to server, synchronous
localStorage.setItem("theme", "dark");
// SESSION STORAGE: ~5MB, cleared on tab close
sessionStorage.setItem("draft", "work in progress");
// WHEN TO USE WHAT:
// Cookies -> auth tokens that the server needs to read, server-side sessions
// localStorage -> user preferences, cached data that persists
// sessionStorage -> temporary per-tab state (form drafts, wizard steps)
// IndexedDB -> large datasets, offline data, complex queriesTimers (setTimeout, setInterval)
setTimeout y setInterval
setTimeout ejecuta un callback una vez tras un retardo; setInterval lo ejecuta repetidamente. Ambos devuelven un ID para cancelación vía clearTimeout/clearInterval. Los timers no son precisos: son retardos mínimos sujetos al event loop, visibilidad de pestaña (throttled en pestañas en segundo plano) y disponibilidad del hilo principal. Los retardos inferiores a 4ms pueden clampse a 4ms en timers anidados.
// setTimeout: run once after a delay (milliseconds)
const timeoutId = setTimeout(() => {
console.log("Runs after 2 seconds");
}, 2000);
// Cancel before it fires
clearTimeout(timeoutId);
// setInterval: run repeatedly every interval
const intervalId = setInterval(() => {
console.log("Runs every 1 second");
}, 1000);
// Stop the interval
clearInterval(intervalId);
// Pass arguments to the callback
setTimeout((greeting, name) => {
console.log(`${greeting}, ${name}`);
}, 1000, "Hello", "Alice");
// NOTE: timers don't guarantee exact timing — they run after the
// minimum delay, but only when the call stack is empty (event loop)setTimeout Recursivo (Mejor que setInterval)
setTimeout recursivo es preferible a setInterval para tareas async recurrentes porque garantiza que la llamada anterior termine antes de que empiece la siguiente: sin ejecuciones solapadas. También permite intervalos dinámicos (por ejemplo, backoff más largo en errores). setInterval puede apilar llamadas si el handler tarda más que el intervalo, causando problemas de rendimiento.
// setInterval problems: doesn't wait for the previous call to finish
// If a call takes longer than the interval, they can stack up.
// Better: recursive setTimeout — guarantees the gap between completions
function poll() {
fetch("/api/status")
.then(r => r.json())
.then(data => {
console.log(data);
setTimeout(poll, 1000); // schedule next AFTER this finishes
})
.catch(err => {
console.error(err);
setTimeout(poll, 5000); // retry with backoff on error
});
}
poll(); // start the loop
// This pattern: always waits for the previous call to complete
// before scheduling the next, avoiding overlap/stacking.requestAnimationFrame (Animaciones Suaves)
requestAnimationFrame (rAF) es la forma correcta de hacer animaciones visuales: se sincroniza con el ciclo de repintado del navegador (~60fps), evita frames innecesarios cuando la pestaña está oculta y produce animaciones más suaves que setInterval. Usa el parámetro timestamp para cálculos de delta-time y mantener la velocidad de animación consistente entre diferentes tasas de refresco. Cancela siempre con cancelAnimationFrame al terminar.
// requestAnimationFrame: syncs with the display refresh (~60fps)
// More efficient than setInterval for visual animations
function animate() {
element.style.left = parseInt(element.style.left || 0) + 2 + "px";
if (parseInt(element.style.left) < 300) {
requestAnimationFrame(animate); // schedule next frame
}
}
requestAnimationFrame(animate);
// Cancel if needed
const rafId = requestAnimationFrame(animate);
cancelAnimationFrame(rafId);
// Timestamp for delta-time calculations
let lastTime = 0;
function loop(timestamp) {
const delta = timestamp - lastTime;
lastTime = timestamp;
update(delta); // frame-rate independent movement
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
// rAF pauses in background tabs (saves CPU/battery)Debounce y Throttle
Debounce espera a una pausa en las llamadas antes de ejecutar (por ejemplo, buscar solo 300ms después de que el usuario deje de escribir). Throttle limita la ejecución a una vez por intervalo (por ejemplo, actualizar la posición de scroll como máximo cada 200ms). Ambos previenen problemas de rendimiento por eventos de alta frecuencia. Debounce = 'agrupar llamadas rápidas en una'; throttle = 'limitar la tasa de llamadas'.
// Debounce: delay execution until calls stop for N ms
// Use for: search input, window resize, button spam
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer); // reset the countdown
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const search = debounce((query) => {
fetchResults(query);
}, 300);
input.addEventListener("input", (e) => search(e.target.value));
// Throttle: execute at most once per N ms
// Use for: scroll, mousemove, drag handlers
function throttle(fn, limit) {
let inThrottle = false;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
const onScroll = throttle(() => {
console.log("Scroll position:", window.scrollY);
}, 200);
window.addEventListener("scroll", onScroll);setTimeout Promisificado (delay)
Envolver setTimeout en una Promise crea una función 'delay' limpia para async/await: mucho más legible que cadenas de callbacks. Este patrón potencia la lógica de reintentos con backoff exponencial (esperar 1s, 2s, 4s entre reintentos), animaciones secuenciales y rate limiting. El helper delay es una de las utilidades diminutas más útiles en JS async moderno.
// A promise-based delay for use with async/await
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function countdown() {
console.log("3...");
await delay(1000);
console.log("2...");
await delay(1000);
console.log("1...");
await delay(1000);
console.log("Go!");
}
countdown();
// Retry with exponential backoff
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url);
if (res.ok) return await res.json();
throw new Error(`HTTP ${res.status}`);
} catch (err) {
if (i === retries - 1) throw err;
const wait = Math.pow(2, i) * 1000; // 1s, 2s, 4s
console.log(`Retry ${i + 1} in ${wait}ms`);
await delay(wait);
}
}
}Map, Set y WeakMap
Map (Clave-Valor con Cualquier Tipo de Clave)
Map es una colección clave-valor propia donde las claves pueden ser de cualquier tipo (objetos, funciones, números), a diferencia de los Objects que coercionan las claves a strings. Map preserva el orden de inserción, tiene una propiedad .size y es directamente iterable. Usa Map cuando necesites claves no string, adiciones/borrados frecuentes o cuando la colección no sea un record/DTO. Usa Object para datos de forma fija.
// Map: like an Object but keys can be ANY type (not just strings)
const map = new Map();
// Keys can be objects, functions, primitives
const objKey = { id: 1 };
map.set("name", "Alice");
map.set(objKey, "data for this object");
map.set(42, "numeric key");
map.set(true, "boolean key");
console.log(map.get("name")); // "Alice"
console.log(map.get(objKey)); // "data for this object"
console.log(map.size); // 4
console.log(map.has(42)); // true
// Iteration preserves insertion order
for (const [key, value] of map) {
console.log(key, value);
}
// Map vs Object:
// - Map keys can be any type; Object keys are strings/symbols
// - Map has .size; Object needs Object.keys().length
// - Map is iterable by default; Object needs Object.entries()
// - Map performs better for frequent add/removeSet (Valores Únicos)
Set almacena valores únicos: perfecto para desduplicación y pruebas de pertenencia. Convertir un array a Set y de vuelta ([...new Set(arr)]) es la forma idiomática de eliminar duplicados. Set.has() es O(1) frente a Array.includes() que es O(n), así que usa Set para colecciones grandes que compruebas frecuentemente. Set no tiene map/filter: espárcelo a array primero.
// Set: collection of unique values (no duplicates)
const set = new Set([1, 2, 3, 2, 1]);
console.log(set); // Set(3) { 1, 2, 3 }
console.log(set.size); // 3
set.add(4);
set.add(4); // ignored (already exists)
console.log(set.has(3)); // true
set.delete(2);
// Iterate (preserves insertion order)
for (const item of set) {
console.log(item);
}
// Common use cases:
// 1. Remove duplicates from an array
const unique = [...new Set([1, 2, 2, 3, 3, 3])]; // [1, 2, 3]
// 2. Check for membership (faster than Array.includes for large sets)
const validIds = new Set([101, 102, 103]);
if (validIds.has(userId)) { /* ... */ }
// 3. Set operations (union, intersection)
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]); // {1,2,3,4}
const intersect = new Set([...a].filter(x => b.has(x))); // {2,3}WeakMap y WeakSet (Referencias Seguras para Memoria)
WeakMap y WeakSet mantienen referencias débiles a objetos: cuando no existen otras referencias, la entrada se recolecta automáticamente. Esto previene fugas de memoria al asociar datos con elementos DOM u otros objetos. Las claves deben ser objetos. WeakMap/WeakSet no son iterables y no tienen .size porque las entradas pueden desaparecer en cualquier momento durante el GC. Úsalos para caché/metadatos ligados al tiempo de vida de objetos.
// WeakMap: keys must be objects; references are "weak"
// (doesn't prevent garbage collection of the key object)
const weakMap = new WeakMap();
let user = { name: "Alice" };
weakMap.set(user, "metadata");
console.log(weakMap.get(user)); // "metadata"
user = null; // remove the only strong reference
// Now the object can be GC'd, and WeakMap entry disappears automatically
// Practical: attach data to DOM elements without memory leaks
const elementData = new WeakMap();
function cacheData(element, data) {
elementData.set(element, data);
}
// When the DOM element is removed, its data is auto-cleaned
// WeakSet: collection of objects, weakly held
const processed = new WeakSet();
function processOnce(obj) {
if (processed.has(obj)) return; // already done
processed.add(obj);
doWork(obj);
}
// WeakMap/WeakSet are NOT iterable (no .size, no for...of)Iteración y Conversión de Map
Los Maps son iterables en orden de inserción vía for...of (entries por defecto), .keys(), .values() o .forEach(). Convierte entre Maps y Objects usando Object.entries() y Object.fromEntries(). Esta conversión bidireccional es útil cuando las APIs esperan objetos planos pero quieres internamente las características de Map. Recuerda: las claves de Object se convierten en tipo string en la conversión.
const map = new Map([
["name", "Alice"],
["age", 30],
["city", "NYC"],
]);
// Iterate entries (default)
for (const [key, value] of map) {
console.log(`${key} = ${value}`);
}
// Iterate keys only
for (const key of map.keys()) {
console.log(key);
}
// Iterate values only
for (const value of map.values()) {
console.log(value);
}
// forEach (callback style)
map.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// Convert to Array
const entries = [...map.entries()]; // [["name","Alice"], ["age",30], ...]
const keys = [...map.keys()]; // ["name", "age", "city"]
const values = [...map.values()]; // ["Alice", 30, "NYC"]
// Convert Object to Map and back
const obj = { a: 1, b: 2 };
const mapFromObj = new Map(Object.entries(obj));
const objFromMap = Object.fromEntries(map);Elegir entre Map, Set, Object y Array
Elige la colección correcta: Arrays para listas ordenadas con duplicados y acceso por índice; Objects para records de forma fija y JSON; Maps para colecciones dinámicas clave-valor con cualquier tipo de clave; Sets para unicidad y búsqueda rápida. Usar la estructura equivocada lleva a código verboso y problemas de rendimiento: por ejemplo, comprobar Array.includes() en un bucle vs Set.has().
// Decision guide:
// Use ARRAY when:
// - You need ordered data with duplicates
// - You need index-based access (arr[5])
// - You'll map/filter/reduce frequently
const todoList = ["buy milk", "walk dog"];
// Use OBJECT when:
// - You have a fixed known shape (like a user record)
// - You need JSON serialization (JSON.stringify)
// - Keys are strings and known at write time
const user = { name: "Alice", age: 30 };
// Use MAP when:
// - Keys are not strings (objects, functions, numbers)
// - You frequently add/remove key-value pairs
// - You need to iterate in insertion order
// - The collection size changes dynamically
const handlers = new Map();
handlers.set(buttonElement, () => onClick());
// Use SET when:
// - You need unique values (no duplicates)
// - You need fast membership testing (.has is O(1))
// - Order doesn't matter much
const seen = new Set();
if (!seen.has(url)) { seen.add(url); visit(url); }Generadores e Iteradores
Iteradores y Symbol.iterator
Un iterador tiene un método next() que devuelve {value, done}. Un iterable implementa Symbol.iterator, que devuelve un iterador. Los iterables integrados (arrays, cadenas, Maps, Sets) funcionan con for...of, spread (...), destructuring y Array.from(). Implementar Symbol.iterator permite que tus objetos personalizados funcionen con todas estas características del lenguaje sin fisuras.
// An iterator is an object with a next() method returning {value, done}
function makeIterator(arr) {
let i = 0;
return {
next() {
return i < arr.length
? { value: arr[i++], done: false }
: { value: undefined, done: true };
},
};
}
const it = makeIterator(["a", "b", "c"]);
console.log(it.next()); // { value: "a", done: false }
console.log(it.next()); // { value: "b", done: false }
console.log(it.next()); // { value: "c", done: false }
console.log(it.next()); // { value: undefined, done: true }
// An iterable implements Symbol.iterator (returns an iterator)
const range = {
from: 1, to: 3,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { done: true };
},
};
},
};
// Now range works with for...of, spread, etc.
console.log([...range]); // [1, 2, 3]Funciones Generadoras (function*)
Las funciones generadoras (function*) usan yield para pausar y reanudar la ejecución: producen valores de forma perezosa, uno a uno. Esto habilita secuencias infinitas, evaluación perezosa y pipelines eficientes en memoria. Los generadores son tanto iteradores como iterables. Cada llamada a next() se ejecuta hasta el siguiente yield (o return). Son la base de los generadores async y los patrones de corrutinas de JS.
// Generators: pause execution with 'yield', resume with next()
function* idGenerator() {
let id = 1;
while (true) {
yield id++; // pause here, return value, resume on next()
}
}
const gen = idGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
// Infinite sequence — only computes values on demand
// Generators are iterables (work with for...of)
function* take(n, iterable) {
for (const item of iterable) {
if (n-- <= 0) return;
yield item;
}
}
function* naturalNumbers() {
let n = 1;
while (true) yield n++;
}
const firstFive = [...take(5, naturalNumbers())];
console.log(firstFive); // [1, 2, 3, 4, 5]yield* (Delegar a Otro Generador)
yield* delega todos los yields a otro iterable o generador: aplana estructuras anidadas y compone generadores de forma limpia. Es el equivalente JS del 'yield from' de Python. Los valores del generador delegado se producen uno a uno como si fueran parte del generador externo. Es la forma estándar de componer generadores recursivamente.
// yield* delegates to another iterable/generator
function* inner() {
yield "a";
yield "b";
}
function* outer() {
yield 1;
yield* inner(); // delegates: yields "a", then "b"
yield* [10, 20]; // also works with arrays
yield 2;
}
console.log([...outer()]); // [1, "a", "b", 10, 20, 2]
// Practical: flatten nested structures
function* flatten(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flatten(item); // recursive delegation
} else {
yield item;
}
}
}
const nested = [1, [2, [3, 4]], 5];
console.log([...flatten(nested)]); // [1, 2, 3, 4, 5]send() del Generador vía next(value)
Los generadores admiten comunicación bidireccional: next(value) pasa un valor (se convierte en el resultado de la última expresión yield), y throw(error) inyecta una excepción en el punto de yield. Esto habilita corrutinas, máquinas de estados y los patrones async basados en generadores que impulsaron las primeras implementaciones de async/await. La primera llamada a next() no puede pasar un valor (nada que lo reciba todavía).
// next(value) passes a value INTO the generator (becomes yield's result)
function* conversation() {
const name = yield "What's your name?"; // receives via next()
const age = yield `Hello ${name}, how old are you?`;
return `${name} is ${age} years old`;
}
const talk = conversation();
console.log(talk.next().value); // "What's your name?"
console.log(talk.next("Alice").value); // "Hello Alice, how old are you?"
console.log(talk.next(30).value); // "Alice is 30 years old"
// throw(error) injects an exception at the yield point
function* safeGen() {
try {
yield "step 1";
} catch (e) {
console.log("Caught:", e.message);
yield "recovered";
}
}
const g = safeGen();
g.next(); // "step 1"
g.throw(new Error("oops")); // "Caught: oops" -> "recovered"Generadores Asíncronos
Los generadores async (async function*) combinan generadores con async/await: cada yield puede producir un valor tras un await. Consúmelos con for await...of. Es ideal para APIs paginadas, datos en streaming o cualquier escenario donde produces valores de forma asíncrona. Son la forma moderna de manejar datos en streaming en JS sin callbacks ni encadenamiento manual de promesas.
// async function* : yields promises, can use await
async function* fetchPages(url) {
let page = 1;
while (true) {
const res = await fetch(`${url}?page=${page}`);
const data = await res.json();
if (data.items.length === 0) break; // no more pages
yield data.items;
page++;
}
}
// Consume with for await...of
(async () => {
for await (const items of fetchPages("/api/products")) {
console.log(`Got ${items.length} items`);
renderItems(items);
}
console.log("All pages loaded");
})();
// Practical: stream data as it arrives
async function* streamLines(response) {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) yield line;
}
}Proxy y Reflect
Fundamentos de Proxy (Interceptar Operaciones)
Proxy permite interceptar y personalizar operaciones fundamentales en un objeto: get, set, has, deleteProperty, ownKeys y más. El objeto handler define 'traps' (como getters/setters pero para todas las propiedades). Casos de uso: validación, logging, valores por defecto, control de acceso, sistemas reactivos (Vue 3 usa Proxy para reactividad). Devuelve true desde set para indicar éxito en modo estricto.
// Proxy wraps an object and intercepts fundamental operations
const target = { name: "Alice", age: 30 };
const handler = {
get(obj, prop) {
console.log(`Reading ${prop}`);
return prop in obj ? obj[prop] : "N/A"; // default value
},
set(obj, prop, value) {
console.log(`Setting ${prop} = ${value}`);
if (prop === "age" && value < 0) return false; // validation
obj[prop] = value;
return true; // indicate success
},
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // logs "Reading name", returns "Alice"
proxy.age = 25; // logs "Setting age = 25"
// proxy.age = -5; // logs "Setting age = -5", rejected
console.log(proxy.unknown); // logs "Reading unknown", returns "N/A"Casos de Uso Comunes de Proxy
Los Proxies habilitan metaprogramación potente: índices negativos, cálculo perezoso de propiedades, vistas de solo lectura, validación, logging y binding de datos reactivo. El sistema de reactividad de Vue 3 usa Proxy para rastrear el acceso a propiedades y disparar re-renders automáticamente. El trade-off es una pequeña sobrecarga de rendimiento, así que usa Proxies donde la abstracción valga la pena, no para cada objeto.
// 1. Negative array indices (like Python)
function negativeArray(arr) {
return new Proxy(arr, {
get(target, prop) {
const idx = Number(prop);
if (Number.isInteger(idx)) {
return target[idx < 0 ? target.length + idx : idx];
}
return target[prop];
},
});
}
const arr = negativeArray(["a", "b", "c"]);
console.log(arr[-1]); // "c"
// 2. Auto-populating / lazy properties
const lazy = new Proxy({}, {
get(target, prop) {
if (!(prop in target)) {
target[prop] = expensiveCompute(prop); // compute on first access
}
return target[prop];
},
});
// 3. Read-only object
const readOnly = new Proxy(data, {
set() { throw new Error("This object is read-only"); },
deleteProperty() { throw new Error("Cannot delete"); },
});API Reflect
Reflect proporciona las mismas operaciones que interceptan los traps de Proxy: úsalo dentro de los traps para reenviar al comportamiento por defecto de forma limpia. Los métodos de Reflect devuelven booleanos (éxito/fallo) en lugar de lanzar, haciéndolos más seguros para lógica condicional. Reflect.ownKeys devuelve todas las claves (strings Y symbols), a diferencia de Object.keys que solo devuelve claves string enumerables. Juntos, Proxy y Reflect forman el toolkit de metaprogramación de JS.
// Reflect provides default behavior for proxy traps and
// functional equivalents of object operations
const obj = { x: 1, y: 2 };
// Reflect.get / Reflect.set (instead of obj[prop])
console.log(Reflect.get(obj, "x")); // 1
Reflect.set(obj, "z", 3); // obj.z = 3
console.log(Reflect.has(obj, "x")); // true (like "x" in obj)
Reflect.deleteProperty(obj, "y"); // delete obj.y
console.log(Reflect.ownKeys(obj)); // ["x", "z"]
// In a Proxy, use Reflect to forward to the default behavior
const proxy = new Proxy({}, {
get(target, prop, receiver) {
console.log(`get ${prop}`);
return Reflect.get(target, prop, receiver); // default behavior
},
set(target, prop, value, receiver) {
console.log(`set ${prop}`);
return Reflect.set(target, prop, value, receiver);
},
});
// Reflect.construct: call a constructor with an array of args
const instance = Reflect.construct(Array, [1, 2, 3]);Objeto Reactivo con Proxy (estilo Vue)
Esta es la idea central de la reactividad de Vue 3: un Proxy intercepta get (para rastrear qué efectos dependen de una propiedad) y set (para disparar esos efectos cuando la propiedad cambia). Esto habilita actualizaciones declarativas de UI: mutas el estado y el framework se re-renderiza automáticamente. Los Proxies hicieron esto posible en ES6; los frameworks anteriores (Vue 2) usaban Object.defineProperty con limitaciones.
// A minimal reactive system using Proxy
function reactive(target) {
const subscribers = new Set();
const handler = {
get(obj, prop) {
track(prop); // record who reads this property
const value = Reflect.get(obj, prop);
return typeof value === "object" && value !== null
? reactive(value) // deeply reactive
: value;
},
set(obj, prop, value) {
const result = Reflect.set(obj, prop, value);
trigger(prop); // notify subscribers
return result;
},
};
let currentEffect = null;
function track(prop) {
if (currentEffect) subscribers.add(currentEffect);
}
function trigger(prop) {
subscribers.forEach(fn => fn());
}
return new Proxy(target, handler);
}
const state = reactive({ count: 0 });
// When state.count changes, effects re-run automaticallyWeb Workers
Crear un Web Worker
Los Web Workers ejecutan JavaScript en un hilo en segundo plano separado, habilitando paralelismo real sin bloquear la UI. El hilo principal y el worker se comunican vía postMessage (los datos se copian/structured-cloned, no se comparten). Los workers no pueden acceder al DOM ni al objeto window: están aislados. Usa workers para tareas intensivas de CPU como procesamiento de imágenes, análisis de archivos grandes o cálculos complejos.
// main.js — workers run JS in a background thread
const worker = new Worker("worker.js");
// Send data to the worker
worker.postMessage({ command: "calculate", data: [1, 2, 3, 4, 5] });
// Receive results from the worker
worker.onmessage = (event) => {
console.log("Result from worker:", event.data);
};
worker.onerror = (error) => {
console.error("Worker error:", error.message);
};
// Terminate when done (frees resources)
// worker.terminate();
// --- worker.js (separate file) ---
// self.onmessage = (event) => {
// const { command, data } = event.data;
// const result = heavyComputation(data);
// self.postMessage(result);
// };Workers Inline (Blob URL)
Los workers inline crean un Worker a partir de una cadena de código vía una Blob URL: sin archivo separado. Es útil para demos, utilidades pequeñas o cuando tu sistema de build no maneja fácilmente archivos worker separados. Recuerda revocar la URL del objeto para evitar fugas de memoria. El código del worker es una cadena, así que pierdes el resaltado de sintaxis del editor y la comprobación de tipos: úsalo con cuidado en producción.
// Create a worker from a string (no separate file needed)
const workerCode = `
self.onmessage = function(e) {
const result = e.data.map(x => x * x);
self.postMessage(result);
};
`;
const blob = new Blob([workerCode], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
worker.postMessage([1, 2, 3, 4]);
worker.onmessage = (e) => console.log(e.data); // [1, 4, 9, 16]
// Clean up the blob URL when done
// URL.revokeObjectURL(blob URL);
// Useful for: single-file demos, bundlers that inline workers,
// or when you can't serve a separate .js fileObjetos Transferibles (Copia Cero)
Normalmente, postMessage copia los datos (structured clone), lo cual es lento para buffers grandes. La transfer list (segundo argumento) MUEVE la propiedad de ArrayBuffer/MessagePort/ImageBitmap al worker con copia cero: casi instantáneo independientemente del tamaño. El buffer original se separa (inutilizable). Usa esto para datasets grandes, procesamiento de imágenes o audio para evitar la sobrecarga de copia.
// Transferable objects move data to a worker WITHOUT copying
// (the original becomes unusable) — much faster for large ArrayBuffers
// Create a large buffer
const buffer = new ArrayBuffer(1024 * 1024 * 10); // 10MB
const view = new Float64Array(buffer);
view[0] = 3.14;
// Transfer the buffer (second arg = transfer list)
worker.postMessage({ data: buffer }, [buffer]);
// After transfer, 'buffer' is detached (length becomes 0)
console.log(buffer.byteLength); // 0 — ownership moved to worker
// Worker receives it normally:
// self.onmessage = (e) => {
// const buf = e.data.data; // full 10MB, no copy
// };
// Transferable types: ArrayBuffer, MessagePort, ImageBitmap
// Use for: large datasets, image data, audio buffersSharedArrayBuffer y Atomics
SharedArrayBuffer permite memoria compartida real entre hilos (sin copia), y Atomics proporciona operaciones thread-safe (add, load, store, compareExchange, wait/notify) sobre ella. Esto habilita algoritmos paralelos de alto rendimiento en JS. Debido a preocupaciones de seguridad Spectre, SharedArrayBuffer requiere cabeceras HTTP de aislamiento cross-origin: sin ellas, está deshabilitado en navegadores modernos. Úsalo para interop de WASM y computación paralela pesada.
// SharedArrayBuffer: memory shared between main thread and workers
// (both can read/write simultaneously) — true shared memory
// const sharedBuffer = new SharedArrayBuffer(1024);
// const view = new Int32Array(sharedBuffer);
// worker.postMessage({ buffer: sharedBuffer });
// Atomics: thread-safe operations on SharedArrayBuffer
// const sharedArray = new Int32Array(sharedBuffer);
// Atomics.add(sharedArray, 0, 1); // atomic increment
// Atomics.load(sharedArray, 0); // atomic read
// Atomics.store(sharedArray, 0, 42); // atomic write
// Atomics.compareExchange(sharedArray, 0, 42, 99); // CAS
// Atomics.wait / notify: block and wake threads
// Worker:
// Atomics.wait(sharedArray, 0, 0); // block until index 0 != 0
// Main:
// Atomics.store(sharedArray, 0, 1);
// Atomics.notify(sharedArray, 0); // wake waiting workers
// NOTE: Requires cross-origin isolation headers:
// Cross-Origin-Opener-Policy: same-origin
// Cross-Origin-Embedder-Policy: require-corpPatrón Worker Pool
Crear workers tiene sobrecarga, así que un worker pool reutiliza un número fijo de workers y encola tareas: como un thread pool en otros lenguajes. Este patrón maximiza la utilización de CPU (un worker por núcleo) evitando el coste de spawnear workers por tarea. El pool despacha tareas a workers libres y las encola cuando todos están ocupados. Esencial para procesar muchos chunks de trabajo independientes.
// A worker pool reuses workers to avoid creation overhead
class WorkerPool {
constructor(workerUrl, size = 4) {
this.workers = [];
this.queue = [];
for (let i = 0; i < size; i++) {
const worker = new Worker(workerUrl);
worker.busy = false;
this.workers.push(worker);
}
}
run(data) {
return new Promise((resolve, reject) => {
const task = { data, resolve, reject };
const worker = this.workers.find(w => !w.busy);
if (worker) {
this.execute(worker, task);
} else {
this.queue.push(task); // wait for a free worker
}
});
}
execute(worker, task) {
worker.busy = true;
worker.onmessage = (e) => {
worker.busy = false;
task.resolve(e.data);
this.next();
};
worker.postMessage(task.data);
}
next() {
const task = this.queue.shift();
const worker = this.workers.find(w => !w.busy);
if (task && worker) this.execute(worker, task);
}
}Manipulación del DOM en Profundidad
querySelector
querySelector devuelve la primera coincidencia, querySelectorAll devuelve una NodeList estática. getElementsByClassName devuelve una HTMLCollection viva. NodeList admite forEach; HTMLCollection no.
const el = document.querySelector('.my-class');
const all = document.querySelectorAll('div.item');
all.forEach(el => console.log(el.textContent));
const live = document.getElementsByClassName('item'); // live
const static = document.querySelectorAll('.item'); // staticCrear e Insertar
createElement crea un elemento nuevo. appendChild añade como último hijo, prepend añade como primero. textContent es más seguro que innerHTML ya que previene XSS.
const div = document.createElement('div');
div.className = 'card';
div.textContent = 'Hello';
div.setAttribute('data-id', '42');
const parent = document.querySelector('#container');
parent.appendChild(div);
parent.prepend(div);Delegación de Eventos
La delegación de eventos vincula un listener a un padre en lugar de muchos a hijos. closest encuentra el ancestro más cercano que coincide con un selector. Maneja elementos añadidos dinámicamente.
document.querySelector('#list').addEventListener('click', (e) => {
const item = e.target.closest('.list-item');
if (!item) return;
console.log('Clicked:', item.dataset.id);
});API classList
classList proporciona métodos para manipular clases CSS de forma segura. toggle devuelve true si se añadió, false si se eliminó. Más limpio que manipular la cadena className.
const el = document.querySelector('.box');
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('dark-mode');
el.classList.replace('old', 'new');
if (el.classList.contains('active')) { /* ... */ }Atributos Dataset
Los atributos data-* almacenan datos personalizados. dataset proporciona acceso camelCase: data-user-id se convierte en dataset.userId. Los valores son siempre strings.
// HTML: <div data-user-id="42" data-role="admin"></div>
const el = document.querySelector('[data-user-id]');
console.log(el.dataset.userId); // "42"
console.log(el.dataset.role); // "admin"
el.dataset.userId = '99';API Canvas
Dibujo Básico
getContext("2d") devuelve el contexto de renderizado 2D. fillRect dibuja un rectángulo relleno, strokeRect dibuja un contorno. Establece fillStyle/strokeStyle antes de dibujar.
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 100, 50);
ctx.strokeStyle = 'blue';
ctx.lineWidth = 3;
ctx.strokeRect(130, 10, 100, 50);Paths y Líneas
moveTo posiciona el cursor sin dibujar. lineTo dibuja una línea. closePath conecta de vuelta al inicio. arc dibuja círculos/arcos.
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(150, 50);
ctx.lineTo(100, 150);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.arc(250, 35, 25, 0, Math.PI * 2);
ctx.fill();Texto y Gradientes
createLinearGradient crea un gradiente. addColorStop define colores en posiciones (0-1). Establece font y textAlign antes de dibujar texto.
const grad = ctx.createLinearGradient(0, 0, 200, 0);
grad.addColorStop(0, 'red');
grad.addColorStop(1, 'blue');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, 200, 100);
ctx.font = '24px Arial';
ctx.fillText('Hello Canvas', 100, 50);Bucle de Animación
requestAnimationFrame se sincroniza con el refresco de pantalla (~60fps). clearRect limpia antes de cada frame. Cancela con cancelAnimationFrame.
let x = 0;
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'green';
ctx.fillRect(x, 50, 30, 30);
x += 2;
if (x > canvas.width) x = 0;
requestAnimationFrame(animate);
}
animate();Manipulación de Imágenes
drawImage renderiza imágenes. getImageData devuelve datos de píxel como arrays RGBA. Manipula píxeles para filtros. putImageData escribe los píxeles modificados de vuelta.
const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0, 200, 150);
const data = ctx.getImageData(0, 0, 200, 150);
for (let i = 0; i < data.data.length; i += 4)
data.data[i] = 255 - data.data[i];
ctx.putImageData(data, 0, 0);
};
img.src = 'photo.jpg';WebSockets
WebSocket Básico
WebSocket proporciona comunicación full-duplex sobre una única conexión TCP. onopen se dispara al conectar, onmessage cuando llegan datos. Maneja siempre onerror.
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => { ws.send('Hello Server'); };
ws.onmessage = (e) => { console.log('Received:', e.data); };
ws.onclose = () => console.log('Disconnected');
ws.onerror = (err) => console.error('Error:', err);Enviar y Recibir JSON
Los datos WebSocket se transmiten como strings o binarios. JSON.stringify/parse habilita intercambio de datos estructurado. Un campo type habilita el enrutamiento de mensajes.
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'message', text: 'Hello' }));
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
switch (data.type) {
case 'message': console.log(data.text); break;
}
};Reconexión
Las conexiones WebSocket pueden caer. El backoff exponencial (2^retries) evita saturar el servidor. Limita el retardo a 30s. Reinicia el contador de reintentos en caso de éxito.
class ReconnectingWS {
constructor(url) { this.url = url; this.retries = 0; this.connect(); }
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => { this.retries = 0; };
this.ws.onclose = () => {
const delay = Math.min(1000 * 2 ** this.retries++, 30000);
setTimeout(() => this.connect(), delay);
};
}
}Datos Binarios
WebSocket admite datos binarios vía ArrayBuffer. Establece binaryType a arraybuffer. DataView proporciona acceso tipado. El binario es más eficiente para datos numéricos.
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
view.setInt32(0, 42);
ws.send(buffer);
ws.binaryType = 'arraybuffer';
ws.onmessage = (e) => {
if (e.data instanceof ArrayBuffer) {
const v = new DataView(e.data);
console.log(v.getInt32(0));
}
};Heartbeat
Los heartbeats detectan conexiones obsoletas. Envía pings y espera pongs. Si no hay pong dentro del timeout, cierra y reconecta. readyState comprueba el estado de la conexión.
setInterval(() => {
if (ws.readyState === WebSocket.OPEN)
ws.send(JSON.stringify({ type: 'ping' }));
}, 30000);
setInterval(() => {
if (Date.now() - lastPong > 60000) ws.close();
}, 10000);Service Workers
Registro
Los service workers se ejecutan en un hilo separado, interceptando peticiones de red. El registro debe ocurrir en HTTPS o localhost. La ubicación del archivo SW determina el scope.
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('Registered:', reg.scope))
.catch(err => console.error('Failed:', err));
}Caché
Estrategia cache-first: sirve desde caché, recurre a red. install pre-cachea assets. fetch intercepta peticiones. Otras estrategias: network-first, stale-while-revalidate.
const CACHE = 'app-v1';
self.addEventListener('install', (e) => {
e.waitUntil(caches.open(CACHE)
.then(c => c.addAll(['/', '/index.html', '/style.css'])));
});
self.addEventListener('fetch', (e) => {
e.respondWith(caches.match(e.request)
.then(cached => cached || fetch(e.request)));
});Background Sync
Background sync difiere acciones hasta que vuelve la conectividad. El evento sync se dispara cuando hay red disponible. Almacena acciones pendientes en IndexedDB.
navigator.serviceWorker.ready.then(reg =>
reg.sync.register('send-messages'));
self.addEventListener('sync', (e) => {
if (e.tag === 'send-messages')
e.waitUntil(sendPendingMessages());
});Notificaciones Push
Las notificaciones push funcionan incluso cuando la app está cerrada. subscribe se registra con un servicio push usando claves VAPID. userVisibleOnly requiere mostrar una notificación.
const reg = await navigator.serviceWorker.ready;
const sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapidPublicKey
});
self.addEventListener('push', (e) => {
const data = e.data.json();
e.waitUntil(self.registration.showNotification(data.title, {
body: data.body
}));
});Actualizar y Activar
skipWaiting activa un SW nuevo inmediatamente. activate limpia cachés antiguas. clients.claim toma el control inmediatamente. Versiona el nombre de caché para disparar actualizaciones.
self.addEventListener('install', (e) => {
self.skipWaiting();
});
self.addEventListener('activate', (e) => {
e.waitUntil(caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE)
.map(k => caches.delete(k)))));
self.clients.claim();
});IndexedDB
Abrir Base de Datos
IndexedDB es una base de datos NoSQL en el navegador. onupgradeneeded se dispara cuando cambia la versión, usado para crear object stores. keyPath define la clave primaria.
const req = indexedDB.open('MyDatabase', 1);
req.onupgradeneeded = (e) => {
const db = e.target.result;
if (!db.objectStoreNames.contains('users'))
db.createObjectStore('users', { keyPath: 'id' });
};
req.onsuccess = (e) => { const db = e.target.result; };Add y Put
Las transacciones agrupan operaciones atómicamente. add falla en claves duplicadas, put sobrescribe. readwrite permite modificaciones. oncomplete se dispara en caso de éxito.
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
store.add({ id: 1, name: 'Alice' }); // Fails if key exists
store.put({ id: 1, name: 'Bob' }); // Overwrites
tx.oncomplete = () => console.log('Saved');Consultar Datos
get recupera por clave. openCursor itera registros. cursor.continue mueve al siguiente. Envuelve en Promesas para uso con async/await.
const tx = db.transaction('users', 'readonly');
const req = tx.objectStore('users').get(1);
req.onsuccess = () => console.log(req.result);
const cursorReq = tx.objectStore('users').openCursor();
cursorReq.onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) { console.log(cursor.value); cursor.continue(); }
};Consultas por Índice
Los índices habilitan consultas en campos no clave. IDBKeyRange crea límites. Los índices deben crearse en onupgradeneeded.
const tx = db.transaction('users', 'readonly');
const index = tx.objectStore('users').index('name');
index.get('Alice').onsuccess = (e) =>
console.log(e.target.result);
const range = IDBKeyRange.bound('A', 'M');
index.openCursor(range).onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) { cursor.continue(); }
};Borrar y Limpiar
delete elimina un solo registro. clear elimina todos los registros. deleteDatabase elimina la base de datos entera. Todas las modificaciones requieren transacciones readwrite.
const tx = db.transaction('users', 'readwrite');
tx.objectStore('users').delete(1); // Delete by key
tx.objectStore('users').clear(); // Clear all
indexedDB.deleteDatabase('MyDatabase'); // Delete DB
tx.oncomplete = () => console.log('Done');WebRTC
Obtener Media del Usuario
getUserMedia solicita acceso a cámara y micrófono. Devuelve un MediaStream. srcObject asigna el stream al elemento video. Requiere HTTPS y permiso del usuario.
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
document.querySelector('video').srcObject = stream;
});Conexión Peer
RTCPeerConnection establece conexiones P2P. addTrack añade media. ontrack recibe el stream remoto. Los candidatos ICE son rutas de red intercambiadas vía signaling.
const pc = new RTCPeerConnection(config);
stream.getTracks().forEach(t => pc.addTrack(t, stream));
pc.ontrack = (e) => { remoteVideo.srcObject = e.streams[0]; };
pc.onicecandidate = (e) => {
if (e.candidate) sendToPeer(e.candidate);
};Offer y Answer
Negociación SDP: el intercambio offer/answer describe formatos de media. setLocalDescription establece el SDP local, setRemoteDescription establece el SDP remoto.
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
sendToPeer(offer);
// Callee:
await pc.setRemoteDescription(offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
sendToPeer(answer);Canales de Datos
Los canales de datos habilitan transferencia arbitraria de datos sobre WebRTC con baja latencia. createDataChannel crea en el offerer. ondatachannel recibe en el answerer.
const channel = pc.createDataChannel('chat');
channel.onopen = () => console.log('Connected');
channel.onmessage = (e) => console.log('Received:', e.data);
channel.send('Hello peer!');
pc.ondatachannel = (e) => {
e.channel.onmessage = (ev) => console.log(ev.data);
};Compartir Pantalla
getDisplayMedia captura pantalla, ventana o pestaña. El navegador muestra un selector. onended se dispara cuando el usuario deja de compartir. Maneja siempre la limpieza.
const stream = await navigator.mediaDevices.getDisplayMedia({
video: { frameRate: 30 }, audio: true
});
video.srcObject = stream;
stream.getVideoTracks()[0].onended = () =>
console.log('Stopped');Optimización de Rendimiento
Debounce y Throttle
Debounce retrasa la ejecución hasta que las llamadas se detienen (bueno para búsqueda). Throttle limita a una vez por intervalo (bueno para scroll). Ambos previenen llamadas excesivas.
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
function throttle(fn, limit) {
let inThrottle;
return (...args) => {
if (!inThrottle) { fn(...args); inThrottle = true;
setTimeout(() => inThrottle = false, limit); }
};
}Web Workers
Los Web Workers ejecutan JavaScript en un hilo separado para tareas intensivas de CPU. Los datos se pasan vía postMessage. Los workers no pueden acceder al DOM.
const worker = new Worker('worker.js');
worker.postMessage({ data: [1, 2, 3] });
worker.onmessage = (e) => console.log('Result:', e.data);
// worker.js
self.onmessage = (e) => {
self.postMessage(e.data.data.map(x => x ** 2));
};Lazy Loading
IntersectionObserver se dispara cuando los elementos entran en el viewport. data-src contiene la URL real; src se establece cuando es visible. Reduce la carga inicial de página para páginas con muchas imágenes.
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));Agrupar Actualizaciones del DOM
DocumentFragment agrupa inserciones del DOM en un único reflow, mucho más rápido que añadir una a una. Minimiza el layout thrashing.
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li);
});
list.appendChild(fragment); // Single reflowGestión de Memoria
WeakMap permite GC de claves, previniendo fugas. Elimina siempre los event listeners cuando se eliminen elementos. Establece a null los objetos grandes para el GC.
const cache = new WeakMap();
cache.set(element, data);
// When element is GC'd, entry is removed
element.removeEventListener('click', handler);
bigArray = null; // Allow GCSeguridad (XSS/CSRF)
Prevención XSS
XSS inyecta scripts maliciosos vía entrada del usuario. Nunca uses innerHTML con datos no confiables. textContent es seguro. Las cabeceras CSP restringen las fuentes de scripts.
// BAD: vulnerable to XSS
element.innerHTML = userInput;
// GOOD: safe alternatives
element.textContent = userInput;
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}Prevención CSRF
CSRF engaña a los usuarios para que realicen acciones no deseadas. Los tokens garantizan que las peticiones vinieron de tu app. Las cookies SameSite=Strict no se envían cross-site. Usa tokens CSRF para operaciones que cambian estado.
const token = document.cookie.match(/csrf_token=([^;]+)/)?.[1];
fetch('/api/data', {
method: 'POST',
headers: { 'X-CSRF-Token': token }
});
// SameSite cookie: Set-Cookie: session=abc; SameSite=StrictContent Security Policy
CSP restringe qué recursos pueden cargarse. default-src es el fallback. script-src controla JavaScript. Empieza con modo report-only antes de imponer.
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data:">Cookies Seguras
HttpOnly impide el acceso JavaScript a cookies, mitigando XSS. Secure garantiza solo HTTPS. SameSite=Strict previene CSRF. Las cookies de sesión deberían usar siempre ambos.
// Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=3600
// HttpOnly: not accessible via JavaScript
// Secure: only sent over HTTPS
// SameSite: Strict | Lax | None
document.cookie // Cannot see HttpOnly cookiesValidación de Entrada
La validación del lado del cliente mejora la UX pero no es seguridad. Valida siempre en el servidor. Usa DOMPurify para saneamiento HTML. Lista blanca de etiquetas permitidas.
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function sanitize(html) {
return DOMPurify.sanitize(html, { ALLOWED_TAGS: ['p', 'br', 'strong'] });
}
// Never trust client input - always validate on server tooPatrones de Diseño
Singleton
Singleton garantiza que solo exista una instancia. El constructor devuelve la instancia existente. Útil para configuración, logging y conexiones a bases de datos.
class Config {
constructor() {
if (Config.instance) return Config.instance;
this.settings = {};
Config.instance = this;
}
}
const c1 = new Config();
const c2 = new Config();
console.log(c1 === c2); // trueObserver/Pub-Sub
El patrón Observer permite a los objetos suscribirse a eventos. on registra, emit dispara, off desuscribe. Fundación de las arquitecturas orientadas a eventos.
class EventEmitter {
constructor() { this.events = {}; }
on(event, cb) { (this.events[event] ||= []).push(cb); }
emit(event, data) { (this.events[event] || []).forEach(cb => cb(data)); }
off(event, cb) { this.events[event] = this.events[event]?.filter(c => c !== cb); }
}Factory
Factory crea objetos sin exponer la lógica de instanciación. El llamador especifica un tipo, la factory decide qué clase instanciar.
class Dog { speak() { return 'Woof'; } }
class Cat { speak() { return 'Meow'; } }
function AnimalFactory(type) {
switch (type) {
case 'dog': return new Dog();
case 'cat': return new Cat();
}
}Patrón Módulo
El patrón módulo encapsula estado privado usando closures. IIFE crea un ámbito privado. Solo los métodos devueltos son públicos.
const counter = (() => {
let count = 0; // Private
return {
increment: () => ++count,
getCount: () => count
};
})();
counter.increment();
console.log(counter.getCount()); // 1Strategy
El patrón Strategy encapsula algoritmos intercambiables. El contexto delega a la estrategia seleccionada. Evita grandes cadenas if/else.
const strategies = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
};
function calculate(strategy, a, b) {
return strategies[strategy]?.(a, b);
}Errores Comunes
Binding de this
this se determina por cómo se llama a una función. Las arrow functions heredan this del ámbito envolvente. Los métodos pierden this al separarse. Usa bind.
const obj = {
name: 'Alice',
greet: function() { console.log(this.name); },
arrow: () => console.log(this.name)
};
obj.greet(); // Alice
obj.arrow(); // undefined
const fn = obj.greet;
fn(); // undefined (lost binding)Coma Flotante
JavaScript usa coma flotante IEEE 754. Usa Number.EPSILON para comparaciones, o multiplica por potencias de 10 para trabajar con enteros.
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
const eps = Number.EPSILON;
console.log(Math.abs(0.1 + 0.2 - 0.3) < eps); // true== vs ===
== realiza coerción de tipos, llevando a resultados sorprendentes. === comprueba tanto tipo como valor sin coerción. Usa siempre === y !==.
console.log(0 == false); // true
console.log('' == false); // true
console.log(null == undefined); // true
console.log(0 === false); // false
console.log('' === false); // false
// Always use ===Closures en Bucles
var tiene ámbito de función, así que todas las closures comparten la misma variable. let tiene ámbito de bloque, creando un nuevo binding por iteración. Usa siempre let/const.
// BUG: all print 3
for (var i = 0; i < 3; i++)
setTimeout(() => console.log(i), 100);
// FIX: let
for (let i = 0; i < 3; i++)
setTimeout(() => console.log(i), 100);Manejo de Errores Async
Las rechazos de promesas no manejados pueden colgar Node.js. Envuelve siempre await en try/catch, o usa .catch(). Escucha eventos unhandledRejection.
// BUG: unhandled rejection
async function fetchData() {
const data = await fetch('/api');
return data.json();
}
// FIX: try/catch
async function fetchDataSafe() {
try { return await (await fetch('/api')).json(); }
catch (err) { console.error('Failed:', err); return null; }
}Fragmentos de JavaScript relacionados
Copy-paste ready code for common tasks.
Map, Filter y Reduce de Arrays
Encadenar map, filter, reduce, find, some y every en arrays.
Deduplicación de Arrays
Deduplicar un array usando Set.
Clonado Profundo
Clonar profundamente objetos, soportando tipos de datos comunes.
Función Debounce
Esperar un período de tiempo después de que un evento se dispare antes de ejecutar; reiniciar el temporizador si se dispara de nuevo durante la espera.
Función Throttle
Limitar una función para ejecutarse como máximo una vez dentro de un intervalo de tiempo.
Control de Concurrencia con Promise.all
Un ejecutor de Promise con límite de concurrencia.
Manejo de Errores con async/await
Envolver funciones async para capturar excepciones de manera uniforme.
Wrapper de Fetch
Envolver fetch con timeout, manejo de errores y análisis JSON.
Operaciones de localStorage
Envolver localStorage con tiempo de expiración y soporte JSON.
Operaciones de Cookie
Envolver operaciones de lectura, escritura y eliminación de Cookie.
Análisis de Parámetros URL
Analizar el query string de una URL en un objeto.
Formateo de Fechas
Formatear una fecha en una cadena especificada.
Formateo de Moneda
Formatear un número como una cadena de moneda con separador de miles.
Generación de Números Aleatorios
Generar números aleatorios y cadenas aleatorias dentro de un rango.
Conversión de Color
Convertir entre colores RGB y HEX.
Generación de UUID
Generar un identificador único compatible con UUID v4.
Truncado de Cadena
Truncar una cadena y añadir puntos suspensivos.
Aplanado de Arrays
Aplanar un array multidimensional en una sola dimensión.
Fusión de Objetos
Fusionar profundamente múltiples objetos.
Verificación de Tipos
Determinar con precisión los tipos de datos de JavaScript.
Delegación de Eventos
Implementar delegación de eventos mediante event bubbling.
Manipulación del DOM
Crear y manipular dinámicamente elementos del DOM.
Validación de Formularios
Una colección de reglas comunes de validación de formularios.
Subida de Archivos
Envolver la subida de archivos con soporte de progreso y fragmentación.
Carga Diferida de Imágenes
Implementar la carga diferida de imágenes con IntersectionObserver.
Copiar al Portapapeles
Un método de copia al portapapeles compatible entre navegadores.
API de Pantalla Completa
Envolver operaciones de pantalla completa del navegador.
Geolocalización
Obtener información de geolocalización del usuario.
Web Worker
Crear un Web Worker para ejecutar tareas que consumen mucho tiempo.
Service Worker
Registrar un Service Worker para caché sin conexión.
Operaciones de IndexedDB
Envolver operaciones CRUD de IndexedDB.
Dibujo con Canvas
Ejemplo básico de dibujo con Canvas.
Was this helpful?