Arrays y objetos

Lección 5 de 8. Casi todos los datos de un programa de JavaScript viven en arrays y objetos. Esta lección recorre los métodos que más se usan, la desestructuración, y lo que más errores causa: que asignar un objeto no lo copia.

Arrays: lo básico

Un array es una lista ordenada de valores de cualquier tipo. Crece y encoge solo, y se accede a cada elemento por su posición, empezando en 0:

const cesta = ["pan", "leche"];
cesta.push("café");          // añade al final
cesta.unshift("miel");       // añade al principio
console.log(cesta, cesta.length);

console.log(cesta[0], cesta.at(-1));      // el primero y el último
console.log(cesta.includes("leche"), cesta.indexOf("té"));

const ultimo = cesta.pop();   // quita y devuelve el último
console.log(ultimo, cesta);
salida[ 'miel', 'pan', 'leche', 'café' ] 4
miel café
true -1
café [ 'miel', 'pan', 'leche' ]

at(-1) cuenta desde el final, cosa que cesta[-1] no hace (daría undefined). indexOf devuelve -1 cuando no encuentra el elemento, y includes, directamente un booleano, que es lo que casi siempre quieres preguntar.

map, filter y reduce

Son los tres métodos que más vas a usar. Ninguno modifica el array original: devuelven uno nuevo (o un valor, en el caso de reduce). Con esta lista de productos:

const productos = [
  { nombre: "pan", precio: 1.2, stock: 10 },
  { nombre: "leche", precio: 0.95, stock: 0 },
  { nombre: "café", precio: 7.5, stock: 3 },
  { nombre: "miel", precio: 4.9, stock: 1 },
];
const conStock = productos.filter((p) => p.stock > 0);
const valores = conStock.map((p) => p.precio * p.stock);
const total = valores.reduce((suma, v) => suma + v, 0);

console.log(conStock.map((p) => p.nombre));
console.log(valores);
console.log("valor del almacén:", total.toFixed(2));
salida[ 'pan', 'café', 'miel' ]
[ 12, 22.5, 4.9 ]
valor del almacén: 39.40

Tubería: filter deja los productos con stock, map calcula el valor de cada uno y reduce los sumafilter, map y reduce: una tubería de datosproductospan 1.2 x10leche 0.95 x0café 7.5 x3miel 4.9 x1filter(stock > 0)pan 1.2 x10café 7.5 x3miel 4.9 x1map(precio x stock)1222.54.9reduce(suma)39.4cada paso devuelve algo nuevo: el array de productos no cambia

filter se queda con los elementos que cumplen la condición, map transforma cada uno y reduce los combina en un solo valor. Esquema: decodigo.com.

reduce es el que más cuesta leer. Recibe una función con dos parámetros, el acumulado hasta ahora y el elemento actual, y el valor inicial del acumulado (el 0 del final). En cada vuelta, lo que devuelve la función pasa a ser el nuevo acumulado. No te saltes ese valor inicial: sin él, reduce sobre un array vacío lanza un error.

Como cada método devuelve un array, se pueden encadenar en una sola expresión, una línea por paso:

const total = productos
  .filter((p) => p.stock > 0)
  .map((p) => p.precio * p.stock)
  .reduce((suma, v) => suma + v, 0);
console.log(total.toFixed(2));
salida39.40

Buscar: find, findIndex, some y every

console.log(productos.find((p) => p.precio > 5));
console.log(productos.findIndex((p) => p.nombre === "miel"));
console.log(productos.some((p) => p.stock === 0));    // ¿alguno agotado?
console.log(productos.every((p) => p.precio < 10));   // ¿todos por debajo de 10?
console.log(productos.find((p) => p.nombre === "kiwi"));
salida{ nombre: 'café', precio: 7.5, stock: 3 }
3
true
true
undefined

La última línea es la que da problemas: si find no encuentra nada, devuelve undefined, y el error llega en la línea siguiente, al usar el resultado:

const kiwi = productos.find((p) => p.nombre === "kiwi");
console.log(kiwi.precio);
salidaTypeError: Cannot read properties of undefined (reading 'precio')

Comprueba el resultado antes de usarlo, o usa kiwi?.precio si no encontrarlo es normal (el encadenamiento opcional de la lección 3).

Ordenar: sort y toSorted

const nombres = ["Luis", "ana", "Álvaro", "Bea"];

const copia = nombres.toSorted((a, b) => a.localeCompare(b, "es"));
console.log(copia);
console.log(nombres);            // intacto

nombres.sort((a, b) => (a < b ? -1 : 1));
console.log(nombres);            // sort sí lo modifica

const porPrecio = productos.toSorted((a, b) => a.precio - b.precio);
console.log(porPrecio.map((p) => p.nombre));
salida[ 'Álvaro', 'ana', 'Bea', 'Luis' ]
[ 'Luis', 'ana', 'Álvaro', 'Bea' ]
[ 'Bea', 'Luis', 'ana', 'Álvaro' ]
[ 'leche', 'pan', 'miel', 'café' ]

Tres cosas que conviene saber. sort modifica el array que ordena; toSorted, más reciente, devuelve uno nuevo y deja el original como estaba. La función que reciben compara dos elementos y devuelve un número negativo si el primero va antes, positivo si va después: por eso a.precio - b.precio ordena de menor a mayor. Y para texto, localeCompare con el idioma pone la «Á» con la «A» y no distingue mayúsculas, mientras que comparar con < ordena por el código de cada carácter y manda «ana» y «Álvaro» al final. Lo de llamar a sort() sin función, que ordena los números como si fueran texto, está en las rarezas de JavaScript.

Objetos

Un objeto es una colección de pares clave y valor. Las claves son cadenas y los valores, cualquier cosa, también funciones:

const nombre = "Ana";
const campo = "ciudad";

const usuario = {
  nombre,                     // igual que nombre: nombre
  edad: 34,
  [campo]: "Quito",           // clave calculada
  saludar() {
    return `Hola, soy ${this.nombre}`;
  },
};

console.log(usuario.saludar());
console.log(usuario["edad"], usuario.ciudad);
console.log(Object.keys(usuario));
console.log("edad" in usuario, Object.hasOwn(usuario, "telefono"));

delete usuario.edad;
console.log(Object.entries(usuario).filter(([, v]) => typeof v !== "function"));
salidaHola, soy Ana
34 Quito
[ 'nombre', 'edad', 'ciudad', 'saludar' ]
true false
[ [ 'nombre', 'Ana' ], [ 'ciudad', 'Quito' ] ]

El punto y los corchetes son equivalentes; los corchetes hacen falta cuando la clave está en una variable o tiene caracteres raros. Object.keys, Object.values y Object.entries convierten el objeto en arrays, y así se le pueden aplicar todos los métodos de arriba.

Desestructurar y propagar

La desestructuración saca valores de un array o de un objeto y los guarda en variables, en una línea:

const [primero, segundo, ...resto] = [10, 20, 30, 40];
console.log(primero, segundo, resto);

const usuario = { nombre: "Ana", edad: 34 };
const { nombre, edad: años, pais = "desconocido" } = usuario;
console.log(nombre, años, pais);

// también en los parámetros de una función
function ficha({ nombre, edad }) {
  return `${nombre} (${edad})`;
}
console.log(ficha(usuario));

// e intercambiar dos variables sin una auxiliar
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b);
salida10 20 [ 30, 40 ]
Ana 34 desconocido
Ana (34)
2 1

En los objetos se pueden renombrar (edad: años) y dar un valor por omisión (pais = "desconocido"). La versión en parámetros es muy común: deja claro qué propiedades usa la función y permite llamarla con un objeto de opciones.

La propagación con ..., que en la lección 4 repartía un array en argumentos, también copia los elementos de un array o las propiedades de un objeto dentro de otro:

const base = { tema: "claro", idioma: "es", avisos: true };
const delUsuario = { tema: "oscuro" };

const config = { ...base, ...delUsuario };
console.log(config);

const juntos = [...[1, 2], 3, ...[4, 5]];
console.log(juntos);
salida{ tema: 'oscuro', idioma: 'es', avisos: true }
[ 1, 2, 3, 4, 5 ]

Si una clave se repite, gana la última: así se combinan unos valores por omisión con los que elige el usuario.

Referencias: la copia que no copia

Este es el concepto más importante de la lección. Una variable no guarda el objeto: guarda una referencia a él. Asignarla a otra variable no copia nada; las dos apuntan al mismo objeto.

const a = { nombre: "Ana", dir: { ciudad: "Quito" } };

const asignado = a;
const superficial = { ...a };
const profunda = structuredClone(a);

a.nombre = "Ana María";
a.dir.ciudad = "Lima";

console.log("asignado:   ", asignado.nombre, asignado.dir.ciudad);
console.log("superficial:", superficial.nombre, superficial.dir.ciudad);
console.log("profunda:   ", profunda.nombre, profunda.dir.ciudad);
console.log({} === {}, a === asignado);
salidaasignado:    Ana María Lima
superficial: Ana Lima
profunda:    Ana Quito
false true

Asignar comparte el objeto; la copia con tres puntos duplica el primer nivel y comparte lo anidado; structuredClone lo duplica todoAsignar, copiar por encima y copiar a fondoconst b = a;asignar: el mismo objetoabnombredirciudadun objeto, dos nombresconst b = { …a };copia superficialabnombredirnombredirciudadlo de dentro, compartidostructuredClone(a)copia profundaabnombredirnombredirciudadciudadnada compartido

Asignar no crea nada nuevo. La copia con ... duplica solo el primer nivel: el objeto dir lo siguen compartiendo. structuredClone duplica todos los niveles. Esquema: decodigo.com.

Por eso la copia superficial ve el cambio de ciudad pero no el de nombre: copió nombre, que es una cadena, pero de dir solo copió la referencia. Y por eso {} === {} es false: === entre objetos no compara su contenido, sino si son el mismo objeto.

structuredClone copia objetos, arrays, fechas, Map y Set, pero no funciones ni instancias de clases con sus métodos:

structuredClone({ nombre: "Ana", saludar() {} });
salidaDOMException [DataCloneError]: saludar() {} could not be cloned.
const no hace el objeto inmutable. Impide reasignar la variable, pero el objeto al que apunta se puede modificar sin problemas: por eso a.nombre = "Ana María" funcionó arriba. Lo que da error es esto:
const lista = [1, 2];
lista.push(3);            // bien: modifica el array
lista = [4, 5];           // error: reasigna la variable
salidaTypeError: Assignment to constant variable.

Map y Set

Además de objetos y arrays, JavaScript tiene dos colecciones más especializadas. Set guarda valores sin repetir; Map, pares clave y valor en los que la clave puede ser de cualquier tipo:

const etiquetas = ["js", "web", "js", "node", "web"];
const unicas = new Set(etiquetas);
console.log(unicas, unicas.size, unicas.has("node"));
console.log([...unicas]);            // de vuelta a array

const visitas = new Map();
const ana = { nombre: "Ana" };
visitas.set(ana, 3);                  // un objeto como clave
visitas.set("anónimo", 12);
console.log(visitas.get(ana), visitas.size);
for (const [quien, n] of visitas) {
  console.log(quien.nombre ?? quien, n);
}
salidaSet(3) { 'js', 'web', 'node' } 3 true
[ 'js', 'web', 'node' ]
3 2
Ana 3
anónimo 12
NecesitasUsa
Una lista ordenada, con posicionesarray
Un registro con campos fijos (nombre, edad…)objeto
Quitar duplicados o preguntar «¿está?» muchas vecesSet
Un diccionario que crece, con claves que no son texto o que se recorre en orden de inserciónMap

JSON: pasar objetos a texto y volver

JSON es el formato de texto con el que se intercambian datos en la web, y su sintaxis viene de los objetos de JavaScript. JSON.stringify convierte un valor en texto y JSON.parse hace lo contrario:

const pedido = {
  id: 7,
  cliente: "Ana",
  fecha: new Date(Date.UTC(2026, 9, 19)),
  notas: undefined,
  extras: new Map([["envio", "rápido"]]),
};

const texto = JSON.stringify(pedido);
console.log(texto);

const deVuelta = JSON.parse(texto);
console.log(typeof deVuelta.fecha, "notas" in deVuelta);

console.log(JSON.stringify({ a: 1, b: [1, 2] }, null, 2));
salida{"id":7,"cliente":"Ana","fecha":"2026-10-19T00:00:00.000Z","extras":{}}
string false
{
  "a": 1,
  "b": [
    1,
    2
  ]
}

No todo sobrevive al viaje: las propiedades con undefined desaparecen, la fecha vuelve como texto y no como fecha, y un Map se queda en un objeto vacío. El tercer argumento de stringify es la sangría, para que el resultado se pueda leer. Leer y escribir archivos JSON está en el ejemplo JSON en JavaScript.

Todo el código de esta lección se ejecutó con Node.js 24 en un contenedor limpio antes de publicarla; las salidas y los errores están copiados de esa ejecución.