TL; DR
Pero hay mucho más por explorar, sigue leyendo ...
JavaScript tiene una semántica poderosa para recorrer las matrices y los objetos de tipo matriz. He dividido la respuesta en dos partes: opciones para matrices genuinas y opciones para cosas que son simplemente como matrices, como el arguments
objeto, otros objetos iterables (ES2015 +), colecciones DOM, etc.
Notaré rápidamente que puede usar las opciones de ES2015 ahora , incluso en motores ES5, al trasladar ES2015 a ES5. Busque "Transpiling ES2015" / "Transpiling ES6" para más ...
Bien, veamos nuestras opciones:
Para matrices reales
Tiene tres opciones en ECMAScript 5 ("ES5"), la versión más ampliamente admitida en este momento y dos más agregadas en ECMAScript 2015 ("ES2015", "ES6"):
- Uso
forEach
y relacionados (ES5 +)
- Usa un
for
bucle simple
- Usar
for-in
correctamente
- Usar
for-of
(usar un iterador implícitamente) (ES2015 +)
- Use un iterador explícitamente (ES2015 +)
Detalles:
1. Uso forEach
y relacionados
En cualquier entorno vagamente moderno (por lo tanto, no IE8) donde tiene acceso a las Array
funciones agregadas por ES5 (directamente o usando polyfills), puede usar forEach
( spec
| MDN
):
var a = ["a", "b", "c"];
a.forEach(function(entry) {
console.log(entry);
});
forEach
acepta una función de devolución de llamada y, opcionalmente, un valor para usar como this
cuando se llama a esa devolución de llamada (no utilizada anteriormente). Se llama a la devolución de llamada para cada entrada en la matriz, en orden, omitiendo las entradas inexistentes en matrices dispersas. Aunque solo utilicé un argumento anterior, la devolución de llamada se llama con tres: el valor de cada entrada, el índice de esa entrada y una referencia a la matriz sobre la que está iterando (en caso de que su función no lo tenga a mano) )
A menos que sea compatible con navegadores obsoletos como IE8 (que NetApps muestra con una participación de mercado de poco más del 4% a partir de este escrito en septiembre de 2016), puede usarlo felizmente forEach
en una página web de uso general sin una cuña. Si necesita admitir navegadores obsoletos, el shimming / polyfilling forEach
se realiza fácilmente (busque "es5 shim" para ver varias opciones).
forEach
tiene el beneficio de que no tiene que declarar las variables de indexación y valor en el ámbito que lo contiene, ya que se proporcionan como argumentos para la función de iteración, y se enfocan tan bien en esa iteración.
Si le preocupa el costo de tiempo de ejecución de realizar una llamada de función para cada entrada de matriz, no se preocupe; detalles .
Además, forEach
es la función de "recorrer a través de todos", pero ES5 definió varias otras funciones útiles de "trabajar a través de la matriz y hacer cosas", que incluyen:
every
(deja de repetirse la primera vez que vuelve la devolución de llamada false
o algo falsey)
some
(deja de repetirse la primera vez que vuelve la devolución de llamada true
o algo verdadero)
filter
(crea una nueva matriz que incluye elementos donde regresa la función de filtro true
y omite los que regresa false
)
map
(crea una nueva matriz a partir de los valores devueltos por la devolución de llamada)
reduce
(construye un valor llamando repetidamente a la devolución de llamada, pasando valores anteriores; consulte la especificación para obtener detalles; útil para sumar el contenido de una matriz y muchas otras cosas)
reduceRight
(como reduce
, pero funciona en orden descendente en lugar de ascendente)
2. Use un for
bucle simple
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 de la matriz no cambiará durante el bucle, y es en el código sensibles al rendimiento (poco probable), una versión ligeramente más complicado agarrar la longitud en la delantera podría ser un pequeño poco más rápido:
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 JavaScript modernos, es raro que necesites sacar ese último jugo.
En ES2015 y superior, puede hacer que sus variables de índice y valor sean locales en el for
ciclo:
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 solo se recrea value
sino que 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 tuviera cinco divs, obtendría "Índice es: 0" si hizo clic en el primero e "Índice es: 4" si hizo clic en el último. Esto no funciona si lo usa en var
lugar de let
.
3. Usar for-in
correctamente
Obtendrá personas que le dicen que use for-in
, pero eso no for-in
es para lo que sirve . for-in
recorre las propiedades enumerables de un objeto , no los índices de una matriz. El pedido no está garantizado , ni siquiera en ES2015 (ES6). ES2015 + define un orden para las propiedades de los objetos (vía [[OwnPropertyKeys]]
, [[Enumerate]]
y las cosas que los usan como Object.getOwnPropertyKeys
), pero no definió que for-in
seguiría ese orden; ES2020 lo hizo, sin embargo. (Detalles en esta otra respuesta ).
Los únicos casos de uso reales para for-in
una matriz son:
- Es una matriz escasa con huecos masivos , o
- Está utilizando propiedades que no son elementos y desea incluirlas en el bucle.
Mirando solo ese primer ejemplo: puede usar for-in
para visitar esos elementos de matriz dispersos si usa las salvaguardas apropiadas:
// `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]);
}
}
Tenga en cuenta los tres controles:
Que el objeto tiene su propia propiedad con ese nombre (no uno que herede de su prototipo), y
Que la clave son todos los dígitos decimales (p. Ej., Forma de cadena normal, no notación científica), y
Que el valor de la clave cuando se coacciona a un número es <= 2 ^ 32 - 2 (que es 4,294,967,294). ¿De dónde viene ese número? Es parte de la definición de un índice de matriz en la especificación . Otros números (no enteros, números negativos, números mayores que 2 ^ 32 - 2) no son índices de matriz. La razón por la que es 2 ^ 32 - 2 es que eso hace que el mayor valor del índice sea menor que 2 ^ 32 - 1 , que es el valor máximo que length
puede tener una matriz . (Por ejemplo, la longitud de una matriz cabe en un entero sin signo de 32 bits). (Apoya a RobG por señalar en un comentario en mi publicación de blog que mi prueba anterior no era del todo correcta).
No harías eso en código en línea, por supuesto. Escribirías una función de utilidad. Quizás:
// 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);
});
4. Uso for-of
(use un iterador implícitamente) (ES2015 +)
ES2015 agregó iteradores a JavaScript. La forma más fácil de usar iteradores es la nueva for-of
declaración. Se parece a esto:
const a = ["a", "b", "c"];
for (const val of a) {
console.log(val);
}
Debajo de las cubiertas, eso obtiene un iterador del conjunto y lo recorre, obteniendo los valores de él. Esto no tiene el problema que for-in
tiene usar , porque usa un iterador definido por el objeto (la matriz), y las matrices definen que sus iteradores iteran a través de sus entradas (no sus propiedades). A diferencia for-in
de ES5, el orden en que se visitan las entradas es el orden numérico de sus índices.
5. Use un iterador explícitamente (ES2015 +)
A veces, es posible que desee utilizar un iterador explícitamente . También puedes hacer eso, aunque es mucho más complicado que for-of
. Se parece a esto:
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 en la especificación. Su next
método devuelve un nuevo objeto de resultado cada vez que lo llama. El objeto resultante tiene una propiedad done
que nos dice si está hecho y una propiedad value
con el valor para esa iteración. ( done
es opcional si lo fuera false
, value
es opcional si lo fuera undefined
).
El significado de value
varía según el iterador; Las matrices admiten (al menos) tres funciones que devuelven iteradores:
values()
: Este es el que usé arriba. Se devuelve un iterador que cada value
es la entrada de la matriz para esa iteración ( "a"
, "b"
y "c"
en el ejemplo anterior).
keys()
: Devuelve un iterador donde cada uno value
es la clave para esa iteración (por lo que para nuestro a
anterior, sería "0"
, entonces "1"
, entonces "2"
).
entries()
: Devuelve un iterador donde cada uno value
es una matriz en el formulario [key, value]
para esa iteración.
Para objetos tipo matriz
Además de las matrices verdaderas, también hay objetos en forma de matriz que tienen una length
propiedad y propiedades con nombres numéricos: NodeList
instancias, el arguments
objeto, etc. ¿Cómo recorremos sus contenidos?
Use cualquiera de las opciones anteriores para matrices
Al menos algunos, y posiblemente la mayoría o incluso todos, los enfoques de matriz anteriores se aplican con frecuencia igualmente bien a objetos similares a una matriz:
Uso forEach
y relacionados (ES5 +)
Las diversas funciones en Array.prototype
son "intencionalmente genéricas" y, por lo general, se pueden usar en objetos tipo matriz a través de Function#call
o Function#apply
. (Consulte la Advertencia para los objetos proporcionados por el host al final de esta respuesta, pero es un problema poco frecuente).
Suponga que desea utilizar forEach
en una Node
's childNodes
propiedad. Harías esto:
Array.prototype.forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
Si va a hacer eso mucho, es posible que desee obtener una copia de la referencia de función en una variable para su reutilización, por ejemplo:
// (This is all presumably in some scoping function)
var forEach = Array.prototype.forEach;
// Then later...
forEach.call(node.childNodes, function(child) {
// Do something with `child`
});
Usa un for
bucle simple
Obviamente, un for
bucle simple se aplica a objetos tipo matriz.
Usar for-in
correctamente
for-in
con las mismas garantías que con una matriz, también debería funcionar con objetos similares a una matriz; se puede aplicar la advertencia para los objetos proporcionados por el host en el n. ° 1 anterior.
Usar for-of
(usar un iterador implícitamente) (ES2015 +)
for-of
usa el iterador proporcionado por el objeto (si lo hay). Eso incluye objetos proporcionados por el host. Por ejemplo, la especificación para NodeList
from querySelectorAll
se actualizó para admitir la iteración. La especificación para el HTMLCollection
de getElementsByTagName
no era.
Use un iterador explícitamente (ES2015 +)
Ver # 4.
Crea una verdadera matriz
Otras veces, es posible que desee convertir un objeto tipo matriz en una matriz verdadera. Hacer eso es sorprendentemente fácil:
Usa el slice
método de matrices
Podemos usar el slice
método de matrices, que al igual que los otros métodos mencionados anteriormente es "intencionalmente genérico" y, por lo tanto, puede usarse con objetos similares a una matriz, como este:
var trueArray = Array.prototype.slice.call(arrayLikeObject);
Entonces, por ejemplo, si queremos convertir a NodeList
en una verdadera matriz, podríamos hacer esto:
var divs = Array.prototype.slice.call(document.querySelectorAll("div"));
Vea la Advertencia para los objetos proporcionados por el host a continuación. En particular, tenga en cuenta que esto fallará en IE8 y versiones anteriores, lo que no le permite usar objetos proporcionados por el host de this
esa manera.
Usar sintaxis de propagación ( ...
)
También es posible usar la sintaxis extendida de ES2015 con motores JavaScript que admiten esta función. Como for-of
, esto usa el iterador proporcionado por el objeto (ver # 4 en la sección anterior):
var trueArray = [...iterableObject];
Entonces, por ejemplo, si queremos convertir una NodeList
matriz verdadera, con sintaxis extendida esto se vuelve bastante sucinto:
var divs = [...document.querySelectorAll("div")];
Utilizar Array.from
Array.from
(especificación) | (MDN) (ES2015 +, pero fácilmente rellenado) crea una matriz a partir de un objeto similar a una matriz, opcionalmente pasando primero las entradas a través de una función de mapeo. Entonces:
var divs = Array.from(document.querySelectorAll("div"));
O si desea obtener una matriz de nombres de etiquetas de los elementos con una clase determinada, usaría la función de mapeo:
// Arrow function (ES2015):
var divs = Array.from(document.querySelectorAll(".some-class"), element => element.tagName);
// Standard function (since `Array.from` can be shimmed):
var divs = Array.from(document.querySelectorAll(".some-class"), function(element) {
return element.tagName;
});
Advertencia para objetos proporcionados por el host
Si usa Array.prototype
funciones con objetos de tipo matriz proporcionados por el host (listas DOM y otras cosas proporcionadas por el navegador en lugar del motor de JavaScript), debe asegurarse de realizar pruebas en sus entornos de destino para asegurarse de que el objeto proporcionado por el host se comporta correctamente . La mayoría se comporta correctamente (ahora), pero es importante realizar una prueba. La razón es que la mayoría de los Array.prototype
métodos que es probable que desee utilizar se basan en el objeto proporcionado por el host que da una respuesta honesta a la [[HasProperty]]
operación abstracta . Al escribir estas líneas, los navegadores hacen un muy buen trabajo de esto, pero la especificación 5.1 permitió la posibilidad de que un objeto proporcionado por el host no sea honesto. Está en §8.6.2 , varios párrafos debajo de la gran mesa cerca del comienzo de esa sección), donde dice:
Los objetos host 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 host particular, de hecho, obtenga y almacene valores de propiedad, pero [[HasProperty]]
siempre genera falso .
(No pude encontrar la verborrea equivalente en la especificación ES2015, pero está obligado a seguir siendo el caso.) Una vez más, a partir de este escrito el host proporcionado por el arreglo de objetos similares en los navegadores modernos [común NodeList
de los casos, por ejemplo] hacer mango [[HasProperty]]
correctamente, pero es importante hacer la prueba).
forEach
y no solofor
. como se dijo, en C # fue un poco diferente, y eso me confundió :)