ES6+ / JavaScript Moderno
Las features que cambiaron JavaScript para siempre. Desde arrow functions hasta modulos, todo lo que necesitas para escribir código JS actual, limpio y expresivo.
Arrow functions
Las arrow functions (=>) son la forma moderna y concisa de escribir funciones. Son más cortas, más legibles en la mayoría de los casos, y tienen una diferencia clave con las funciones clásicas: no tienen su propio this, sino que heredan el this del scope donde fueron definidas (lexical this). Esto las hace ideales para callbacks y métodos funcionales como map y filter.
// Sintaxis completa
const sumar = (a, b) => {
return a + b;
};
// Return implicito — si es una sola expresion, no necesitás {}
const duplicar = n => n * 2; // un parametro: sin parentesis
const sumar2 = (a, b) => a + b; // varios parametros: con parentesis
// Devolver un objeto — necesitás parentesis alrededor
const crearUser = (nombre, edad) => ({ nombre, edad });
// Sin parentesis {} se lee como bloque de codigo, no como objeto
// Arrow como callback (el uso mas comun)
const numeros = [1, 2, 3, 4, 5];
const pares = numeros.filter(n => n % 2 === 0); // [2, 4]
const dobles = numeros.map(n => n * 2); // [2, 4, 6, 8, 10]
// Sin cuerpo (para efectos secundarios)
numeros.forEach(n => console.log(n));
// Arrow vs funcion clasica para this
const usuario = {
nombre: "Ana",
saludos: ["hola", "que tal"],
// Funcion clasica: this depende de COMO se llama
mostrarClasico: function() {
this.saludos.forEach(function(saludo) {
// this es undefined (o Window en modo no-estricto)
// console.log(this.nombre); // ERROR
});
},
// Arrow: this se hereda del scope padre (usuario)
mostrarModerno() {
this.saludos.forEach(saludo => {
console.log(`${this.nombre}: ${saludo}`); // OK!
});
}
};
Cuando NO usar arrow functions
No usés arrow functions cuando necesites tu propio this: métodos de objetos, constructores o manejadores de eventos donde this deba referirse al elemento del DOM. Tampoco sirven como métodos de prototipo. Para esos casos usá funciones clásicas o shorthand de método (nombre() {}).
Template literals
Los template literals usan backticks (`` ` ``) en vez de comillas y permiten tres cosas que las cadenas normales no pueden: interpolación de expresiones con ${}, múltiples líneas sin concatenar, y la posibilidad de aplicarles funciones de etiquetado (tagged templates). Son la forma estándar de trabajar con strings en JavaScript moderno, reemplazando completamente la concatenación con +.
const nombre = "Mundo";
const edad = 25;
// Interpolacion de expresiones
const saludo = `Hola, ${nombre}!`; // "Hola, Mundo!"
const info = `Tenés ${edad} años`; // "Tenés 25 años"
// Cualquier expresion dentro de ${}
const calculo = `El doble es ${edad * 2}`; // "El doble es 50"
const ternario = `${edad >= 18 ? "mayor" : "menor"}`; // "mayor"
// Multilinea (conserva los saltos de linea)
const html = `
<div class="card">
<h2>${nombre}</h2>
<p>Edad: ${edad}</p>
</div>
`;
// Expresiones complejas
const usuario = { nombre: "Ana", rol: "admin" };
const badge = `${usuario.rol === "admin"
? '<span class="badge-admin">Admin</span>'
: ''}`;
// Llamadas a funciones dentro de templates
function formatear(precio) {
return `$${precio.toFixed(2)}`;
}
const item = `Total: ${formatear(99.5)}`; // "Total: $99.50"
Destructuring
El destructuring permite extraer valores de objetos y arrays y asignarlos a variables en una sola linea. Es una de las features más usadas de ES6 porque hace el código mucho más legible, especialmente cuando trabajás con funciones que reciben objetos con muchas propiedades, respuestas de APIs, o cuando intercambiás valores entre variables.
// === Destructuring de objetos ===
const usuario = {
nombre: "Ana",
edad: 25,
email: "ana@mail.com",
direccion: { ciudad: "BA", pais: "Argentina" }
};
// Basico
const { nombre, edad } = usuario;
console.log(nombre); // "Ana"
console.log(edad); // 25
// Renombrar variables
const { nombre: userName, edad: userAge } = usuario;
console.log(userName); // "Ana"
// Valores por defecto
const { nombre: n, telefono = "no tiene" } = usuario;
console.log(telefono); // "no tiene"
// Destructuring anidado
const { direccion: { ciudad } } = usuario;
console.log(ciudad); // "BA"
// En parametros de funcion (super usado)
function mostrar({ nombre, edad, rol = "user" }) {
console.log(`${nombre} (${edad}) — ${rol}`);
}
mostrar({ nombre: "Ana", edad: 25 }); // "Ana (25) — user"
// === Destructuring de arrays ===
const colores = ["rojo", "verde", "azul", "amarillo"];
// Por posicion
const [primero, segundo] = colores;
console.log(primero); // "rojo"
console.log(segundo); // "verde"
// Saltar elementos
const [uno, , tres] = colores;
console.log(tres); // "azul"
// Con Rest — el resto en un array
const [rojo, verde, ...resto] = colores;
console.log(resto); // ["azul", "amarillo"]
// Intercambiar variables (swap)
let a = 1, b = 2;
[b, a] = [a, b];
console.log(a, b); // 2 1
// Destructuring en loops
const usuarios = [
{ nombre: "Ana", edad: 25 },
{ nombre: "Carlos", edad: 30 }
];
for (const { nombre, edad } of usuarios) {
console.log(`${nombre}: ${edad}`);
}
En la practica, el destructuring de objetos en parámetros de función es probablemente el uso más frecuente. Te permite que las funciones reciban un solo objeto con opciones en vez de muchos parámetros individuales (lo que se conoce como el options object pattern), y además hace que el código se autodocumente porque los nombres de las propiedades quedan visibles en la firma de la función.
// Options object pattern con destructuring
function crearFetch({ url, method = "GET", headers = {}, body = null }) {
console.log(`${method} ${url}`, headers, body);
}
crearFetch({
url: "/api/users",
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ nombre: "Ana" })
});
// Mucho mejor que:
// function crearFetch(url, method, headers, body) { ... }
// crearFetch("/api/users", "POST", {...}, "..."); // que es cada arg?
Spread y Rest
El operador spread (...) y el operador rest usan la misma sintaxis (...) pero hacen cosas opuestas. El spread "desarma" un array u objeto en elementos individuales. El rest agrupa elementos individuales en un array u objeto. La diferencia está en el contexto: spread aparece donde se esperan elementos (en una asignacion o llamado a funcion), y rest aparece donde se declaran parametros.
// === SPREAD — expande elementos ===
// Copiar arrays
const original = [1, 2, 3];
const copia = [...original]; // [1, 2, 3]
console.log(original === copia); // false (nuevo array)
// Concatenar arrays
const a = [1, 2];
const b = [3, 4];
const combinado = [...a, ...b]; // [1, 2, 3, 4]
// Agregar elementos
const conExtra = [0, ...a, 99]; // [0, 1, 2, 99]
// Convertir iterable a array
const chars = [..."hola"]; // ["h", "o", "l", "a"]
const setArr = [...new Set([1,2,2,3])]; // [1, 2, 3]
// Pasar elementos como argumentos
const nums = [5, 2, 8, 1, 9];
const maximo = Math.max(...nums); // 9
// === SPREAD en objetos (clonar y mergear) ===
const base = { nombre: "Ana", edad: 25 };
const clon = { ...base }; // nuevo objeto
const actualizado = { ...base, edad: 26 }; // sobrescribe edad
const conExtra = { ...base, email: "a@b.c" }; // agrega propiedad
// Mergear multiples objetos (el ultimo gana en conflictos)
const defaults = { tema: "dark", lang: "es", fontSize: 16 };
const userPrefs = { lang: "en", fontSize: 14 };
const config = { ...defaults, ...userPrefs };
// { tema: "dark", lang: "en", fontSize: 14 }
// === REST — agrupa elementos ===
// En parametros de funcion
function sumarTodos(...numeros) {
return numeros.reduce((acc, n) => acc + n, 0);
}
sumarTodos(1, 2, 3, 4); // 10
// Rest con otros parametros fijos
function log(tipo, ...mensajes) {
mensajes.forEach(m => console.log(`[${tipo}] ${m}`));
}
log("ERROR", "archivo no encontrado", "permiso denegado");
// Rest en destructuring de arrays
const [primero, segundo, ...resto] = [1, 2, 3, 4, 5];
console.log(resto); // [3, 4, 5]
// Rest en destructuring de objetos
const { nombre, ...datos } = { nombre: "Ana", edad: 25, pais: "AR" };
console.log(datos); // { edad: 25, pais: "AR" }
Spread es shallow copy
El spread solo copia un nivel. Si el objeto tiene propiedades que son objetos o arrays anidados, esas referencias se comparten. Para una copia profunda usá structuredClone(obj) (nativo desde ES2022) o librerías como Lodash (_.cloneDeep()).
Optional chaining y nullish coalescing
El optional chaining (?.) permite acceder a propiedades anidadas de forma segura sin que el programa crashee si alguna parte de la cadena es null o undefined. En vez de verificar cada nivel manualmente, el ?. cortocircuita la expresion y devuelve undefined tan pronto como encuentra un valor nulo. Es uno de los features más útiles de ES2020, especialmente cuando trabajás con datos de APIs que pueden tener propiedades faltantes.
// Sin optional chaining (verbose y repetitivo)
const user = { perfil: { direccion: { ciudad: "BA" } } };
const ciudad = user
&& user.perfil
&& user.perfil.direccion
&& user.perfil.direccion.ciudad; // "BA"
// Con optional chaining (limpio y seguro)
const ciudad2 = user?.perfil?.direccion?.ciudad; // "BA"
// Si alguna parte es null/undefined, devuelve undefined
const pais = user?.perfil?.direccion?.pais; // undefined
const tel = user?.telefono?.numero; // undefined
// Sin error, sin crash
// Con llamadas a metodos
const doc = document.querySelector(".no-existe");
const texto = doc?.textContent; // undefined (no tira error)
// Con indices de arrays
const items = [10, 20, 30];
const tercero = items?.[2]; // 30
const decimo = items?.[9]; // undefined
// Con funciones
const api = { fetchUsers: null };
const users = api.fetchUsers?.(); // undefined (no llama si no existe)
El nullish coalescing (??) devuelve el valor de la derecha solo si el de la izquierda es null o undefined. Es similar a ||, pero con una diferencia crucial: || reacciona a todos los valores falsy (incluyendo 0, "", y false), mientras que ?? solo reacciona a null y undefined. Esto lo hace perfecto para valores por defecto donde 0 o "" son valores válidos que no deberían ser reemplazados.
// || vs ??
// Con || — reacciona a CUALQUIER valor falsy
const cantidad = 0;
const default1 = cantidad || 10; // 10 (mal! 0 es valido)
// Con ?? — solo reacciona a null y undefined
const default2 = cantidad ?? 10; // 0 (bien! respeta el 0)
// Otros ejemplos donde || falla pero ?? no:
"" ?? "vacio"; // "" (respeta el string vacio)
false ?? true; // false
0 ?? "cero"; // 0
null ?? "default"; // "default"
undefined ?? "def"; // "def"
// Combinacion perfecta: optional chaining + nullish coalescing
const usuario = {
perfil: { nombre: "Ana", bio: null }
};
const bio = usuario?.perfil?.bio ?? "Sin biografia"; // "Sin biografia"
const nombre = usuario?.perfil?.nombre ?? "Anonimo"; // "Ana"
// En parametros de configuracion
function crearBoton({ texto = "Click", size = "md" } = {}) {
const tamanio = size ?? "md";
const label = texto ?? "Boton";
}
crearBoton({ texto: "", size: null });
// texto queda "" (vacio pero valido)
// size usa el fallback "md" porque es null
Metodos de arrays
Los métodos funcionales de arrays son posiblemente lo más poderoso que trajo ES6+. En vez de escribir loops manuales para transformar, filtrar o buscar elementos, usás métodos que reciben una función (callback) y devuelven un resultado. Esto produce código más declarativo (describe qué querés hacer, no cómo), más fácil de leer, y más fácil de testear. Los más importantes son map, filter, reduce, find, some, every e includes.
const productos = [
{ nombre: "Mouse", precio: 25, categoria: "perifericos" },
{ nombre: "Teclado", precio: 45, categoria: "perifericos" },
{ nombre: "Monitor", precio: 200, categoria: "pantallas" },
{ nombre: "Webcam", precio: 60, categoria: "perifericos" },
{ nombre: "SSD", precio: 80, categoria: "almacenamiento" }
];
// map — transforma cada elemento, devuelve NUEVO array del mismo largo
const nombres = productos.map(p => p.nombre);
// ["Mouse", "Teclado", "Monitor", "Webcam", "SSD"]
const conIva = productos.map(p => ({
...p,
precioConIva: +(p.precio * 1.21).toFixed(2)
}));
// filter — devuelve nuevo array solo con los que cumplen la condicion
const baratos = productos.filter(p => p.precio < 50);
// [Mouse, Teclado]
const perifericos = productos.filter(p => p.categoria === "perifericos");
// [Mouse, Teclado, Webcam]
// find — devuelve el PRIMER elemento que cumple la condicion (o undefined)
const monitor = productos.find(p => p.nombre === "Monitor");
// { nombre: "Monitor", precio: 200, categoria: "pantallas" }
const caro = productos.find(p => p.precio > 100);
// { nombre: "Monitor", ... } (solo el primero)
const numeros = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// reduce — reduce el array a UN SOLO valor
// callback recibe: acumulador, elemento actual, indice, array
const suma = numeros.reduce((acc, n) => acc + n, 0); // 55
// Contar frecuencias
const palabras = ["hola", "mundo", "hola", "js", "mundo", "hola"];
const conteo = palabras.reduce((acc, p) => {
acc[p] = (acc[p] || 0) + 1;
return acc;
}, {}); // { hola: 3, mundo: 2, js: 1 }
// Agrupar por propiedad
const porCategoria = productos.reduce((acc, p) => {
const cat = p.categoria;
if (!acc[cat]) acc[cat] = [];
acc[cat].push(p);
return acc;
}, {});
// { perifericos: [...], pantallas: [...], almacenamiento: [...] }
// some — devuelve true si AL MENOS UNO cumple la condicion
const hayCaro = productos.some(p => p.precio > 100); // true
const hayGratis = productos.some(p => p.precio === 0); // false
// every — devuelve true si TODOS cumplen la condicion
const todosTienenPrecio = productos.every(p => p.precio > 0); // true
const todosBaratos = productos.every(p => p.precio < 50); // false
// includes — verifica si un valor EXACTO esta en el array
const frutas = ["manzana", "banana", "naranja"];
frutas.includes("banana"); // true
frutas.includes("pera"); // false
numeros.includes(5); // true
// includes con NaN (uno de los pocos casos donde funciona correctamente)
[1, NaN, 3].includes(NaN); // true
Estos métodos se pueden encadenar porque cada uno devuelve un nuevo array. Esto permite expresar transformaciones complejas en una cadena legible, en vez de anidar loops. El patrón tipico es filtrar primero, luego transformar, y finalmente reducir si necesitás un valor único.
// Encadenamiento de metodos (method chaining)
// "Nombres de perifericos que cuestan menos de 100"
const resultado = productos
.filter(p => p.categoria === "perifericos") // [Mouse, Teclado, Webcam]
.filter(p => p.precio < 100) // [Mouse, Teclado, Webcam]
.map(p => p.nombre); // ["Mouse", "Teclado", "Webcam"]
console.log(resultado);
// "Precio total de los perifericos"
const totalPerifericos = productos
.filter(p => p.categoria === "perifericos")
.reduce((acc, p) => acc + p.precio, 0);
console.log(totalPerifericos); // 130
// Otros metodos utiles
const nums = [3, 1, 4, 1, 5, 9];
nums.sort((a, b) => a - b); // [1, 1, 3, 4, 5, 9]
nums.reverse(); // invierte el array IN-PLACE
nums.slice(1, 4); // [1, 3, 4] (no modifica original)
nums.splice(2, 1, 99); // elimina 1 elem desde pos 2, inserta 99
nums.indexOf(4); // 3 (posicion)
nums.findLast(n => n > 3); // 9 (ultimo que cumple, ES2023)
// flat — aplana arrays anidados
const anidado = [1, [2, 3], [4, [5, 6]]];
anidado.flat(); // [1, 2, 3, 4, [5, 6]]
anidado.flat(Infinity); // [1, 2, 3, 4, 5, 6]
// flatMap — map + flat en un solo paso
const palabras = ["hola mundo", "como estas"];
palabras.flatMap(s => s.split(" ")); // ["hola", "mundo", "como", "estas"]
Inmutabilidad
map, filter, slice, concat y flatMap devuelven arrays nuevos sin modificar el original. En cambio, sort, reverse y splice modifican el array in-place. Para sort/reverse inmutables usá [...arr].sort() o arr.toSorted() (ES2023).
Metodos de objetos
Object tiene métodos estáticos que te permiten trabajar con objetos de forma funcional, similar a los métodos de arrays. Los más usados son Object.keys(), Object.values() y Object.entries() que convierten un objeto en arrays de claves, valores o pares clave-valor respectivamente. Esto te permite usar los métodos de arrays sobre objetos. Además, Object.assign() y el spread operator ({...obj}) se usan para clonar y mergear objetos.
const usuario = {
nombre: "Ana",
edad: 25,
ciudad: "Buenos Aires"
};
// Object.keys — array de claves
Object.keys(usuario); // ["nombre", "edad", "ciudad"]
// Object.values — array de valores
Object.values(usuario); // ["Ana", 25, "Buenos Aires"]
// Object.entries — array de pares [clave, valor]
Object.entries(usuario);
// [["nombre", "Ana"], ["edad", 25], ["ciudad", "Buenos Aires"]]
// Combinado con metodos de arrays
// Obtener un array de strings "clave: valor"
Object.entries(usuario)
.map(([k, v]) => `${k}: ${v}`);
// ["nombre: Ana", "edad: 25", "ciudad: Buenos Aires"]
// Filtrar propiedades por valor
const adulto = Object.entries(usuario)
.filter(([k, v]) => typeof v === "string")
.reduce((obj, [k, v]) => ({ ...obj, [k]: v }), {});
// { nombre: "Ana", ciudad: "Buenos Aires" }
// Object.assign — clonar/mergear (shallow)
const defaults = { tema: "dark", lang: "es", debug: false };
const config = Object.assign({}, defaults, { lang: "en" });
// { tema: "dark", lang: "en", debug: false }
// Nota: spread ({...a, ...b}) es mas comun hoy, pero assign
// es util cuando el target es un objeto existente
// Object.fromEntries — convertir pares de vuelta a objeto
const entries = [["a", 1], ["b", 2], ["c", 3]];
const obj = Object.fromEntries(entries); // { a: 1, b: 2, c: 3 }
// Caso practico: filtrar claves de un objeto
const datos = { nombre: "Ana", password: "123", email: "a@b.c" };
const publico = Object.fromEntries(
Object.entries(datos).filter(([k]) => k !== "password")
); // { nombre: "Ana", email: "a@b.c" }
// Object.freeze — hace el objeto inmutable
const constante = Object.freeze({ a: 1, b: 2 });
constante.a = 99; // silenciosamente ignorado (en modo estricto tira error)
Object.isFrozen(constante); // true
// Object.hasOwnProperty — verificar si una propiedad es propia (no heredada)
"nombre" in usuario; // true (incluye heredadas)
usuario.hasOwnProperty("nombre"); // true (solo propias)
usuario.hasOwnProperty("toString"); // false (es heredada de Object)
Modulos ES
Los módulos ES (ES Modules o ESM) son el sistema oficial de módulos de JavaScript. Permiten dividir tu código en archivos separados, cada uno con su propio scope, y exportar/importar funcionalidades entre ellos. Antes de ES6 se usaban patrones como IIFEs, CommonJS (require/module.exports) o AMD, pero hoy ESM es el estándar nativo soportado por todos los navegadores modernos y por Node.js.
// === EXPORTAR (utils.js) ===
// Export nombrado — uno por uno
export const PI = 3.14159;
export function sumar(a, b) { return a + b; }
export const restar = (a, b) => a - b;
// Export nombrado al final
function multiplicar(a, b) { return a * b; }
function dividir(a, b) { return a / b; }
export { multiplicar, dividir };
// Export por defecto — uno solo por archivo
// Es lo que se importa si no especificás nombre
export default function saludar(nombre) {
return `Hola, ${nombre}!`;
}
// Exportar un objeto/clase como default
export default class Calculadora {
sumar(a, b) { return a + b; }
}
// === IMPORTAR (app.js) ===
// Import por defecto (nombre libre)
import saludar from "./utils.js";
// Import nombrados (entre llaves, nombre exacto)
import { PI, sumar, restar } from "./utils.js";
// Renombrar al importar
import { multiplicar as mult, dividir as div } from "./utils.js";
// Importar todo como objeto namespace
import * as utils from "./utils.js";
utils.sumar(2, 3); // 5
utils.PI; // 3.14159
// Combinar default y nombrados
import saludar, { PI, sumar } from "./utils.js";
// Importar solo para efectos secundarios (ej: polyfills)
import "./polyfills.js";
Para usar módulos ES en el navegador, necesitás agregar type="module" al tag <script>. Esto habilita el modo estricto automáticamente, hace que las variables sean locales al módulo (no globales), y permite que el navegador resuelva las importaciones. Los módulos se cargan de forma diferida (equivalente a defer), por lo que no necesitás agregar ese atributo. En el proyecto WebForge, todos los archivos JS usan type="module".
<!-- Sin type="module": variables globales, sin import/export -->
<script src="js/app.js"></script>
<!-- Con type="module": scope local, import/export, modo estricto -->
<script type="module" src="js/app.js"></script>
<!-- Los modulos se cargan en diferido automaticamente -->
<!-- Equivalente a: <script type="module" src="app.js" defer></script> -->
Consejos de modulos
Usá export default para la funcionalidad principal del archivo (una clase, una función principal, un componente). Usá export nombrados para todo lo demás. Mantené los imports organizados al tope del archivo. No mezcles CommonJS (require) con ES Modules (import) en el mismo archivo. En Node.js, usá "type": "module" en package.json para habilitar ESM.
Probá en MiniDevTools
Si querés experimentar con lo que vimos en esta sección, probá el Minifier CSS/JS/HTML.