¿Cómo puedo recorrer en bucle todas las entradas de una matriz utilizando JavaScript?
Pensé que era algo como esto:
forEach(instance in theArray)
Donde elArreglo
es mi matriz, pero esto parece ser incorrecto.
TL;DR
for-in
a menos que lo utilices con salvaguardas o al menos seas consciente de por qué te puede picar.for-of
(sólo en ES2015+),Array#forEach
(spec
| MDN
) (o sus parientes some
y similares) (sólo ES5+),for
a la antigua,for-in
con salvaguardas.
Pero hay mucho más que explorar, sigue leyendo...JavaScript tiene una poderosa semántica para hacer bucles a través de arrays y objetos tipo array. He dividido la respuesta en dos partes: Opciones para arrays genuinos, y opciones para cosas que son sólo array-like, como el objeto arguments
, otros objetos iterables (ES2015+), colecciones DOM, etc.
Me gustaría señalar rápidamente que puedes utilizar las opciones de ES2015 ahora, incluso en motores ES5, transpilando ES2015 a ES5. Busca "ES2015 transpiling" / "ES6 transpiling" para saber más...
Bien, veamos nuestras opciones:
Tienes tres opciones en ECMAScript 5 ("ES5"), la versión más ampliamente soportada en este momento, y dos más añadidas en ECMAScript 2015 ("ES2015", "ES6"):
forEach
y afines (ES5+)for
.for-in
correctamentefor-of
(usar un iterador implícitamente) (ES2015+)forEach
y similaresEn cualquier entorno vagamente moderno (por tanto, no en IE8) donde tengas acceso a las características Array
añadidas por ES5 (directamente o usando polyfills), puedes usar forEach
(spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
acepta una función de callback y, opcionalmente, un valor para usar como this
cuando se llama a ese callback (no se usa arriba). La llamada de retorno es llamada para cada entrada del array, en orden, saltando las entradas inexistentes en los arrays dispersos. Aunque arriba sólo he utilizado un argumento, la llamada de retorno se llama con tres: El valor de cada entrada, el índice de esa entrada y una referencia al array sobre el que se está iterando (en caso de que tu función no lo tenga ya a mano).
A menos que estés soportando navegadores obsoletos como IE8 (que NetApps muestra en poco más del 4% de la cuota de mercado a partir de este escrito en septiembre de 2016), puedes utilizar felizmente forEach
en una página web de propósito general sin un shim. Si necesitas soportar navegadores obsoletos, es fácil hacer un shimming/polyfilling de forEach
(busca "es5 shim" para varias opciones).
forEach
tiene la ventaja de que no es necesario declarar las variables de indexación y de valor en el ámbito contenedor, ya que se suministran como argumentos a la función de iteración, y por lo tanto están bien delimitadas sólo para esa iteración.
Si te preocupa el coste en tiempo de ejecución de hacer una llamada a la función para cada entrada del array, no lo hagas; detalles.
Además, forEach
es la función de "bucle a través de todos ellos", pero ES5 definió varias otras funciones útiles para "trabajar a través del array y hacer cosas", incluyendo:
every
(detiene el bucle la primera vez que el callback devuelve false
o algo falso)some
(detiene el bucle la primera vez que la llamada de retorno devuelve true
o algo verdadero)filter
(crea un nuevo array incluyendo los elementos en los que la función de filtrado devuelve true
y omitiendo los que devuelve false
)map
(crea un nuevo array a partir de los valores devueltos por la llamada de retorno)reduce
(construye un valor llamando repetidamente a la llamada de retorno, pasando valores anteriores; ver la especificación para los detalles; útil para sumar el contenido de un array y muchas otras cosas)reduceRight
(como reduce
, pero funciona en orden descendente en lugar de ascendente)for
.A veces las viejas formas son las mejores:
var index;
var a = ["a", "b", "c"];
for (index = 0; index < a.length; ++index) {
console.log(a[index]);
}
Si la longitud del array no va a cambiar durante el bucle, y está en un código sensible al rendimiento (poco probable), una versión un poco más complicada que agarre la longitud por adelantado podría ser un tiny poco más rápida:
var index, len;
var a = ["a", "b", "c"];
for (index = 0, len = a.length; index < len; ++index) {
console.log(a[index]);
}
Y/o contando hacia atrás:
var index;
var a = ["a", "b", "c"];
for (index = a.length - 1; index >= 0; --index) {
console.log(a[index]);
}
Pero con los motores modernos de JavaScript, es raro que necesites sacarle ese último jugo.
En ES2015 y superior, puedes hacer que tus variables de índice y valor sean locales al bucle for
:
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
let value = a[index];
console.log(index, value);
}
//console.log(index); // would cause "ReferenceError: index is not defined"
//console.log(value); // would cause "ReferenceError: value is not defined"
let a = ["a", "b", "c"];
for (let index = 0; index < a.length; ++index) {
let value = a[index];
console.log(index, value);
}
try {
console.log(index);
} catch (e) {
console.error(e); // "ReferenceError: index is not defined"
}
try {
console.log(value);
} catch (e) {
console.error(e); // "ReferenceError: value is not defined"
}
Y cuando haces eso, no sólo value
sino también index
se recrea para cada iteración del bucle, lo que significa que los cierres creados en el cuerpo del bucle mantienen una referencia al index
(y value
) creado para esa iteración específica:
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
let divs = document.querySelectorAll("div");
for (let index = 0; index < divs.length; ++index) {
divs[index].addEventListener('click', e => {
console.log("Index is: " + index);
});
}
<div>zero</div>
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
Si tuvieras cinco divs, obtendrías "Index is: 0" si se hace clic en el primero y "El índice es: 4" si haces clic en el último. Esto no funciona si usas var
en lugar de let
.
for-in
correctamenteLa gente te dirá que uses for-in
, pero para eso no sirve for-in
. for-in
recorre las propiedades enumerables de un objeto, no los índices de un array. El orden no está garantizado, ni siquiera en ES2015 (ES6). ES2015+ define un orden para las propiedades de los objetos (a través de [[OwnPropertyKeys]]
, [[Enumerate]]
, y cosas que las usan como Object.getOwnPropertyKeys
), pero no define que for-in
seguirá ese orden. (Detalles en esta otra respuesta.)
Los únicos casos reales de uso de for-in
en un array son:
for-in
para visitar esos elementos del array sparese si usas las salvaguardas adecuadas:
// `a` is a sparse array
var key;
var a = [];
a[0] = "a";
a[10] = "b";
a[10000] = "c";
for (key in a) {
if (a.hasOwnProperty(key) && // These checks are
/^0$|^[1-9]\d*$/.test(key) && // explained
key <= 4294967294 // below
) {
console.log(a[key]);
}
}
Fíjate en las tres comprobaciones:
longitud
de un array. (Por ejemplo, la longitud de un array cabe en un entero sin signo de 32 bits.) (Agradecimientos a RobG por señalar en un comentario en mi entrada del blog que mi prueba anterior no era del todo correcta.)
No harías eso en código inline, por supuesto. Escribirías una función de utilidad. Tal vez:
// Utility function for antiquated environments without `forEach`
var hasOwn = Object.prototype.hasOwnProperty;
var rexNum = /^0$|^[1-9]\d*$/;
function sparseEach(array, callback, thisArg) {
var index;
for (var key in array) {
index = +key;
if (hasOwn.call(a, key) &&
rexNum.test(key) &&
index <= 4294967294
) {
callback.call(thisArg, array[key], index, array);
}
}
}
var a = [];
a[5] = "five";
a[10] = "ten";
a[100000] = "one hundred thousand";
a.b = "bee";
sparseEach(a, function(value, index) {
console.log("Value at " + index + " is " + value);
});
for-of
(usar un iterador implícitamente) (ES2015+)ES2015 añade iteradores a JavaScript. La forma más sencilla de utilizar iteradores es la nueva sentencia for-of
. Su aspecto es el siguiente:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Debajo de las tapas, que obtiene un iterator de la matriz y bucles a través de él, obtener los valores de la misma. Esto no tiene el problema que tiene el uso de for-in
, porque utiliza un iterador definido por el objeto (el array), y los arrays definen que sus iteradores recorren sus entradas (no sus propiedades). A diferencia de for-in
en ES5, el orden en que se visitan las entradas es el orden numérico de sus índices.
A veces, es posible que quieras utilizar un iterador explícitamente. También puedes hacerlo, aunque es mucho más complicado que for-of
. Se ve así:
const a = ["a", "b", "c"];
const it = a.values();
let entry;
while (!(entry = it.next()).done) {
console.log(entry.value);
}
El iterador es un objeto que coincide con la definición de Iterador de la especificación. Su método next
devuelve un nuevo *objeto resultadocada vez que se le llama. El objeto resultado tiene una propiedad,
done, que nos indica si ha terminado, y una propiedad
valuecon el valor de esa iteración. (
donees opcional si fuera
false,
valuees opcional si fuera
undefined). El significado de
valor` varía dependiendo del iterador; los arrays soportan (al menos) tres funciones que devuelven iteradores:
valores()
: Esta es la que he usado arriba. Devuelve un iterador en el que cada valor
es la entrada del array para esa iteración ("a"
, "b"
y "c"
en el ejemplo anterior).: Devuelve un iterador en el que cada
valores la clave de esa iteración (así que para nuestro
ade arriba, sería
"0", luego
"1", luego
"2"`).: Devuelve un iterador donde cada
valores un array de la forma
[clave, valor]` para esa iteración.Aparte de los verdaderos arrays, también hay objetos similares a los arrays que tienen una propiedad length
y propiedades con nombres numéricos: instancias de NodeList
, el objeto arguments
, etc. ¿Cómo podemos recorrer su contenido?
Al menos algunos, y posiblemente la mayoría o incluso todos los enfoques de los arrays anteriores se aplican con frecuencia igualmente bien a los objetos tipo array:
Utilizar forEach
y similares (ES5+)
Las diversas funciones de Array.prototype
son "intencionadamente genéricas" y normalmente pueden usarse en objetos tipo array mediante Function#call
o Function#apply
. (Véase el Caveat para objetos proporcionados por el host al final de esta respuesta, pero es un problema poco frecuente).
Supón que quieres usar forEach
en la propiedad childNodes
de un Node
. Deberías hacer esto:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Haz algo con child
.
});
Si vas a hacer esto muchas veces, puede que quieras coger una copia de la referencia de la función en una variable para reutilizarla, por ejemplo
// (Todo esto es presumiblemente en alguna función de alcance)
var forEach = Array.prototype.forEach;
// Luego, más tarde...
forEach.call(node.childNodes, function(child) {
// Haz algo con child
.
});
Utilizar un simple bucle for
Obviamente, un bucle simple for
se aplica a los objetos tipo array.
Utilizar "for-in" correctamente
El bucle for-in
con las mismas garantías que con un array debería funcionar también con objetos tipo array; la advertencia para los objetos proporcionados por el host en el número 1 puede aplicarse.
Utilizar for-of
(utilizar un iterador implícitamente) (ES2015+)
for-of
utilizará el iterador proporcionado por el objeto (si lo hay); tendremos que ver cómo funciona esto con los distintos objetos tipo array, especialmente los proporcionados por el host. Por ejemplo, la especificación para el NodeList
de querySelectorAll
fue actualizada para soportar la iteración. La especificación para el HTMLCollection
de getElementsByTagName
no lo fue.
Utilizar un iterador explícitamente (ES2015+) Ver #4, habrá que ver cómo funcionan los iteradores.
En otras ocasiones, puedes querer convertir un objeto tipo array en un verdadero array. Hacerlo es sorprendentemente fácil:
Usa el método slice
de los arrays
Podemos utilizar el método slice
de los arrays, que al igual que los otros métodos mencionados anteriormente es "intencionadamente genérico" y por lo tanto se puede utilizar con objetos tipo array, así
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Así, por ejemplo, si queremos convertir un NodeList
en un verdadero array, podríamos hacer lo siguiente
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Ver la Caveat para objetos proporcionados por el host más abajo. En particular, ten en cuenta que esto fallará en IE8 y anteriores, que no te permiten usar objetos proporcionados por el host como this
de esa manera.
Utilizar sintaxis de propagación (..
)
También es posible utilizar la [sintaxis de propagación] de ES2015 (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) con los motores de JavaScript que soportan esta característica:
var trueArray = [...iterableObject];
Así, por ejemplo, si queremos convertir un NodeList
en un verdadero array, con la sintaxis de propagación esto se vuelve bastante sucinto:
var divs = [...document.querySelectorAll("div")];
Utilizar Array.from
(spec) | (MDN)
Array.from
(ES2015+, pero fácilmente polyfilled) crea un array a partir de un objeto tipo array, pasando opcionalmente las entradas por una función de mapeo primero. Así:
var divs = Array.from(document.querySelectorAll("div"));
O si quisieras obtener un array con los nombres de las etiquetas de los elementos con una clase determinada, utilizarías la función de mapeo
// Función Arrow (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Función estándar (ya que Array.from
se puede calzar):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Si utilizas funciones Array.prototype
con objetos tipo array provistos por el host (listas DOM y otras cosas proporcionadas por el navegador en lugar del motor JavaScript), debes asegurarte de hacer pruebas en tus entornos de destino para comprobar que el objeto proporcionado por el host se comporta correctamente. La mayoría se comportan correctamente (ahora), pero es importante hacer pruebas. La razón es que la mayoría de los métodos de Array.prototype
que probablemente quieras utilizar dependen de que el objeto proporcionado por el host dé una respuesta honesta a la operación abstracta [[HasProperty]]
. En el momento de escribir esto, los navegadores hacen un muy buen trabajo, pero la especificación 5.1 permite la posibilidad de que un objeto proporcionado por el host no sea honesto. Está en §8.6.2, varios párrafos por debajo de la gran tabla cerca del comienzo de esa sección), donde dice:
Los objetos anfitriones pueden implementar estos métodos internos de cualquier manera, a menos que se especifique lo contrario; por ejemplo, una posibilidad es que
[[Get]]
y[[Put]]
para un objeto anfitrión en particular efectivamente obtengan y almacenen los valores de las propiedades, pero[[HasProperty]]
siempre genera false. (No he podido encontrar la verborrea equivalente en la especificación de ES2015, pero seguro que sigue siendo así). De nuevo, en el momento de escribir esto, los objetos tipo array comunes proporcionados por el host en los navegadores modernos [instancias deNodeList
, por ejemplo] manejan correctamente[[HasProperty]]
, pero es importante probarlo).
**Nota: Esta respuesta está irremediablemente desfasada. Para un enfoque más moderno, mira los métodos disponibles en un array. Los métodos de interés podrían ser:
La forma estándar de iterar un array en JavaScript es un bucle vainilla for
:
var length = arr.length,
element = null;
for (var i = 0; i < length; i++) {
element = arr[i];
// Do something with element
}
Tenga en cuenta, sin embargo, que este enfoque sólo es bueno si usted tiene una matriz densa, y cada índice está ocupado por un elemento. Si el array es disperso, entonces puedes tener problemas de rendimiento con este enfoque, ya que iterarás sobre un montón de índices que realmente no existen en el array. En este caso, un bucle for .. in
podría ser una mejor idea. **Sin embargo, debe utilizar las salvaguardias apropiadas para asegurarse de que sólo se actúa sobre las propiedades deseadas del array (es decir, los elementos del array), ya que el bucle for..in
también se enumerará en los navegadores antiguos, o si las propiedades adicionales se definen como enumerables
.
En ECMAScript 5 habrá un método forEach en el prototipo del array, pero no está soportado en los navegadores antiguos. Así que para poder usarlo de forma consistente debes tener un entorno que lo soporte (por ejemplo, Node.js para JavaScript del lado del servidor), o usar un "Polyfill". Sin embargo, el "Polyfill" para esta funcionalidad es trivial y, dado que facilita la lectura del código, es un buen "polyfill" a incluir.
Si quieres hacer un bucle sobre un array, utiliza el bucle estándar de tres partes for
.
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
}
Puedes obtener algunas optimizaciones de rendimiento almacenando en caché miMatriz.longitud
o iterando sobre ella hacia atrás.