¿For-each sobre un array en JavaScript?

¿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.

Solución

TL;DR

  • No utilices for-in a menos que lo utilices con salvaguardas o al menos seas consciente de por qué te puede picar.
  • Tus mejores opciones suelen ser
  • Un bucle for-of (sólo en ES2015+),
  • Array#forEach ([spec][1] | [MDN][2]) (o sus parientes some y similares) (sólo ES5+),
  • un simple bucle for a la antigua,
  • o 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:

    Para los Arrays reales

    Tienes tres opciones en [ECMAScript 5][3] ("ES5"), la versión más ampliamente soportada en este momento, y dos más añadidas en [ECMAScript 2015][4] ("ES2015", "ES6"):

  1. Utilizar forEach y afines (ES5+)
  2. Utilizar un bucle simple for.
  3. Usar for-in correctamente
  4. Usar for-of (usar un iterador implícitamente) (ES2015+)
  5. Utilizar un iterador explícitamente (ES2015+) Detalles:

    1. Usar forEach y similares

    En 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][1] | [MDN][2]):

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][5] (detiene el bucle la primera vez que el callback devuelve false o algo falso)
  • [some][6] (detiene el bucle la primera vez que la llamada de retorno devuelve true o algo verdadero)
  • [filter][7] (crea un nuevo array incluyendo los elementos en los que la función de filtrado devuelve true y omitiendo los que devuelve false)
  • [map][8] (crea un nuevo array a partir de los valores devueltos por la llamada de retorno)
  • [reduce][9] (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][10] (como reduce, pero funciona en orden descendente en lugar de ascendente)

    2. Utilice un simple bucle 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.

3. Usa for-in correctamente

La gente te dirá que uses for-in, pero [para eso no sirve for-in][11]. 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][12]), pero no define que for-in seguirá ese orden. (Detalles en [esta otra respuesta][13].) Los únicos casos reales de uso de for-in en un array son:

  • Es un [arrays escaso][14] con grandes huecos en él, o
  • Estás usando propiedades que no son elementos y quieres incluirlas en el bucle Mirando sólo el primer ejemplo: Puedes usar 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  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 de `NodeList`, por ejemplo] **manejan ** correctamente `[[HasProperty]]`, pero es importante probarlo).
  [1]: https://tc39.github.io/ecma262/#sec-array.prototype.foreach
  [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
  [3]: http://ecma-international.org/ecma-262/5.1/
  [4]: http://www.ecma-international.org/ecma-262/6.0/index.html
  [5]: https://tc39.github.io/ecma262/#sec-array.prototype.every
  [6]: https://tc39.github.io/ecma262/#sec-array.prototype.some
  [7]: https://tc39.github.io/ecma262/#sec-array.prototype.filter
  [8]: https://tc39.github.io/ecma262/#sec-array.prototype.map
  [9]: https://tc39.github.io/ecma262/#sec-array.prototype.reduce
  [10]: https://tc39.github.io/ecma262/#sec-array.prototype.reduceright
  [11]: http://blog.niftysnippets.org/2010/11/myths-and-realities-of-forin.html
  [12]: https://tc39.github.io/ecma262/#sec-object.getownpropertynames
  [13]: https://stackoverflow.com/a/30919039/157247
  [14]: http://en.wikipedia.org/wiki/Sparse_array
  [15]: https://tc39.github.io/ecma262/#sec-function.prototype.call
  [16]: https://tc39.github.io/ecma262/#sec-function.prototype.apply
  [17]: https://tc39.github.io/ecma262/#sec-array.from
  [18]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
  [19]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
Comentarios (24)

**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:

  • forEach
  • map
  • filtro
  • zip
  • reducir
  • cada
  • algunos

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.

Comentarios (4)

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.

Comentarios (5)