Arrays dispersos en JavaScript: holes y casos límite | Nicolás GarzónUn array disperso tiene una longitud que incluye posiciones donde no existe una propiedad de elemento.
JavaScript
const values = new Array(3);
values.length;
Object.hasOwn(values, 0);
JavaScript
const sparse = new Array(1);
const explicit = [undefined];
JavaScript
sparse.length;
explicit.length;
JavaScript
Object.hasOwn(sparse, 0);
Object.hasOwn(explicit, 0);
Ambas lecturas devuelven undefined.
JavaScript
sparse[0];
explicit[0];
La existencia de la propiedad es la diferencia.
JavaScript
const first = new Array(3);
const second = [1, , 3];
JavaScript
const third = [1];
third.length = 3;
Asignar un índice lejano:
JavaScript
const fourth = [];
fourth[5] = "A";
JavaScript
const fifth = ["A", "B", "C"];
delete fifth[1];
JavaScript
fifth.length;
Object.hasOwn(fifth, 1);
Para retirar y cerrar el espacio utiliza splice o toSpliced.
JavaScript
const values = ["A", "B", "C"];
delete values[1];
values[2];
values.length;
delete elimina una propiedad; no desplaza elementos.
JavaScript
values.splice(1, 1);
Métodos basados en callbacks como estos normalmente no llaman el callback para posiciones vacías:
forEach
map
filter
flatMap
some
every
reduce
reduceRight
JavaScript
const sparse = new Array(3);
let calls = 0;
sparse.forEach(() => calls++);
calls;
JavaScript
const sparse = [1, , 3];
const mapped = sparse.map(
(value) => value * 2,
);
JavaScript
mapped.length;
Object.hasOwn(mapped, 1);
La posición vacía continúa vacía.
JavaScript
const sparse = [1, , 3];
const filtered = sparse.filter(() => true);
filtered;
Solo incluye posiciones existentes que pasaron la condición.
El iterador de valores visita cada índice hasta length y produce undefined para holes.
JavaScript
const sparse = new Array(3);
[...sparse];
JavaScript
for (const value of sparse) {
console.log(value);
}
Imprime undefined tres veces.
entries() también entrega pares para esas posiciones.
includes trata holes como undefined.
JavaScript
new Array(1).includes(undefined);
indexOf omite posiciones vacías.
JavaScript
new Array(1).indexOf(undefined);
find y findIndex visitan los índices y entregan undefined para posiciones vacías.
Estas diferencias hacen que los holes sean una fuente frecuente de sorpresa.
JavaScript
const values = [1, , 3];
1 in values;
Object.hasOwn(values, 1);
in también considera propiedades heredadas. Object.hasOwn pregunta únicamente por una propiedad propia.
JavaScript
const sparse = new Array(3);
const dense = Array.from(sparse);
JavaScript
dense;
Object.hasOwn(dense, 0);
Spread produce un resultado similar.
JavaScript
const zeros = Array.from(
{ length: 3 },
() => 0,
);
JavaScript
const emptySlots = Array.from(
{ length: 3 },
() => null,
);
Un valor explícito comunica mejor el estado que una propiedad ausente.
JavaScript
const rows = new Array(3).fill({ value: 0 });
Esto densifica, pero utiliza la misma referencia en cada posición.
JavaScript
rows[0] === rows[1];
Para objetos independientes usa Array.from con una factory.
JavaScript
const values = ["A"];
values[-1] = "B";
values[1.5] = "C";
values["name"] = "D";
Son propiedades normales del objeto array.
JavaScript
values.length;
Object.keys(values);
Los métodos de array basados en longitud normalmente no las recorren como elementos.
length está restringida al rango de longitud de arrays. Asignaciones inválidas pueden lanzar RangeError.
No uses arrays como mapas dispersos de IDs enormes.
JavaScript
items[customerId] = customer;
Si las claves no son posiciones compactas, un objeto o Map comunica mejor el modelo y evita una longitud engañosa.
Los motores optimizan arrays densos y pueden representar arrays dispersos de otra manera.
No dependas de una complejidad concreta para todas las implementaciones, pero evita holes accidentales cuando buscas una lista ordinaria:
- Son menos claros.
- Diferentes APIs los tratan distinto.
- Pueden impedir optimizaciones.
- Aumentan el riesgo de errores de límite.
JavaScript
const slots = new Array(5);
slots[2] = reservation;
JavaScript
const slots = Array.from(
{ length: 5 },
() => null,
);
slots[2] = reservation;
null comunica una posición existente sin reserva.
- Creer que un hole es una propiedad con
undefined.
- Utilizar
delete para retirar un elemento de una lista.
- Esperar que todos los métodos visiten holes igual.
- Usar
new Array(length).map(...).
- Crear objetos independientes con
fill({}).
- Guardar IDs grandes como índices.
- Utilizar propiedades no numéricas esperando que afecten
length.
- Conservar holes cuando un valor explícito sería más claro.
- Un hole es una posición sin propiedad propia.
- Leerlo devuelve
undefined, pero no existe como elemento.
delete crea holes y no cambia length.
- Métodos como
map y forEach omiten holes.
for...of, spread e includes los tratan como undefined.
filter y Array.from pueden producir arrays densos.
- Valores explícitos como
null suelen comunicar mejor una ausencia intencional.
- Para claves dispersas usa un objeto o
Map.
¿Qué diferencias hay entre estos resultados?
JavaScript
const sparse = new Array(1);
sparse.includes(undefined);
sparse.indexOf(undefined);
Respuesta
includes devuelve true porque trata el hole como undefined; indexOf devuelve -1 porque omite posiciones vacías.
Map presenta una colección clave-valor donde las claves conservan su identidad y pueden ser de cualquier tipo.