Obtenga un contador / índice de bucle usando para ... de sintaxis en JavaScript

318

Precaución:

la pregunta todavía se aplica a los for…ofbucles.> No use for…inpara iterar sobre una matriz , úsela para iterar sobre las propiedades de un objeto. Dicho esto, esto


Entiendo que la for…insintaxis básica en JavaScript se ve así:

for (var obj in myArray) {
    // ...
}

Pero, ¿cómo obtengo el contador / índice de bucle ?

Sé que probablemente podría hacer algo como:

var i = 0;
for (var obj in myArray) {
    alert(i)
    i++
}

O incluso los buenos viejos:

for (var i = 0; i < myArray.length; i++) {
    var obj = myArray[i]
    alert(i)
}

Pero prefiero usar el for-inbucle más simple . Creo que se ven mejor y tienen más sentido.

¿Hay una manera más simple o más elegante?


En Python es fácil:

for i, obj in enumerate(myArray):
    print i
hobbes3
fuente
66
No use para ... en para matrices. Y de todos modos, itera sobre los nombres de las propiedades, no los valores de las propiedades.
Felix Kling
1
Es una matriz, no un objeto, ¿verdad? Por lo tanto, alert(obj)?
Rocket Hazmat

Respuestas:

548

for…initera sobre nombres de propiedades, no valores, y lo hace en un orden no especificado (sí, incluso después de ES6). No debe usarlo para iterar sobre matrices. Para ellos, existe el forEachmétodo de ES5 que pasa tanto el valor como el índice a la función que le asigna:

var myArray = [123, 15, 187, 32];

myArray.forEach(function (value, i) {
    console.log('%d: %s', i, value);
});

// Outputs:
// 0: 123
// 1: 15
// 2: 187
// 3: 32

O ES6 Array.prototype.entries, que ahora es compatible con las versiones actuales del navegador:

for (const [i, value] of myArray.entries()) {
    console.log('%d: %s', i, value);
}

Sin embargo, para iterables en general (donde usaría un for…ofbucle en lugar de a for…in), no hay nada incorporado:

function* enumerate(iterable) {
    let i = 0;

    for (const x of iterable) {
        yield [i, x];
        i++;
    }
}

for (const [i, obj] of enumerate(myArray)) {
    console.log(i, obj);
}

manifestación

Si realmente for…inquisiera decir , enumerar propiedades, necesitaría un contador adicional. Object.keys(obj).forEachpodría funcionar, pero solo incluye propiedades propias ; for…inincluye propiedades enumerables en cualquier parte de la cadena del prototipo.

Ry-
fuente
2
Oh ok Estaba confundido. Pensé que JavaScript for-in era lo mismo que Python. Gracias por la aclaración.
hobbes3
1
@quantumpotato: lets son vars con alcance de bloque. consts no cambian
Ry-
1
Esta fue una respuesta detallada, gracias por ello. Realmente aclaró todas las cosas discutidas
Dheeraj Bhaskar
1
pregunta estúpida, pero ¿qué significan realmente% d y% s, o podrían ser cualquier letra que quiero que sean?
klewis
2
@klewis: %dformatea un número entero y %sformatea una cadena. Se basan en printf . Una especificación está en progreso en console.spec.whatwg.org/#formatter .
Ry-
163

En ES6, es bueno usar para - de bucle. Puede obtener un índice de esta manera

for (let [index, val] of array.entries()) {
        // your code goes here    
}

Tenga en cuenta que Array.entries()devuelve un iterador , que es lo que le permite trabajar en el ciclo for-of; no confunda esto con Object.entries () , que devuelve una matriz de pares clave-valor.

rushUp
fuente
99
¡Esta es una respuesta mucho mejor que la aceptada!
trusktr
3
Creo que esta solución es mejor que la de ForEach one ... Utiliza el nomral para ... de la sintaxis de bucle, y no tiene que usar una función separada. En otras palabras, es sintácticamente mejor. El OP parece haber querido esto.
u8y7541
1
entries()es devolver un objeto vacío: {}. ¿Alguna idea de por qué sería eso? Mi arrayes una matriz de objetos.
Joshua Pinter
@JoshuaPinter intente en Object.entries(array)lugar dearray.entries()
tonyg
2
Se supone que debe hacer eso, Joshua: el objeto es un iterador, un objeto con un next()método que devolverá entradas posteriores en la matriz cada vez que se lo llame. No hay datos (visibles) en él; obtienes los datos en el objeto subyacente llamando next(), lo que a menudo se hace detrás de escena. cc @tonyg
Shog9
26

Qué tal esto

let numbers = [1,2,3,4,5]
numbers.forEach((number, index) => console.log(`${index}:${number}`))

Donde array.forEacheste método tiene un indexparámetro que es el índice del elemento actual que se procesa en la matriz.

Sanjay Shr
fuente
1
mejor respuesta aquí
codepleb
44
La respuesta elegida se publicó 6 años antes de esta y ya tiene lo mismo ...
Deiv
Foreach no es bueno para la optimización, ya breakque no está disponible.
smartworld-dm
20

Solución para colecciones de matriz pequeña:

for (var obj in arr) {
    var i = Object.keys(arr).indexOf(obj);
}

arr - ARRAY, obj - CLAVE del elemento actual, i - CONTADOR / ÍNDICE

Aviso: las teclas de método () no están disponibles para IE versión <9, debe usar el código Polyfill . https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

estaba borracho
fuente
77
Sugeriría: use un contador en su lugar, increméntelo en bucle.
mayankcpdixit
2
Agregando a mayankcpdixit, use un contador en su lugar porque indexOf podría tener un impacto negativo en el rendimiento.
Dean Liu
1
Cuanto más grande sea el objeto, más lento será. Esto no escala.
weltschmerz
2
Esto es un poco sin sentido lento y complicado debido var i = 0;y i++;es más corto y más eficiente. Además, no funciona para propiedades enumerables que no son propiedades propias.
Ry-
1
@trusktr: Y si es necesario ... aún no deberías usar esto. Simplemente modifique el mostrador cuando modifique la colección. Si no tiene que estar en su lugar, realice una buena transformación funcional.
Ry-
13

For-in-loops itera sobre las propiedades de un objeto. No los use para matrices, incluso si a veces funcionan.

Las propiedades de los objetos no tienen índice, son todas iguales y no es necesario ejecutarlas en un orden determinado. Si desea contar las propiedades, deberá configurar el contador adicional (como lo hizo en su primer ejemplo).

bucle sobre una matriz:

var a = [];
for (var i=0; i<a.length; i++) {
    i // is the index
    a[i] // is the item
}

recorrer un objeto:

var o = {};
for (var prop in o) {
    prop // is the property name
    o[prop] // is the property value - the item
}
Bergi
fuente
3
Nunca haga lo (var i=0; i<a.length; i++)que se desperdicia recursos. Uso(var i=0, var len = a.length; i<len; i++)
Félix Sanz
16
@FelixSanz: ¿Residuos de recursos? De ninguna manera. Esa es una microoptimización prematura que casi nunca es necesaria, y var i=0; i<a.length; i++)de todos modos es el patrón de bucle estándar que está optimizado por cada motor javascript decente.
Bergi
3
@FelixSanz: Sí, y var i=0; i<a.length; i++es la mejor práctica.
Bergi
1
BESO . Si escribe bucles donde realmente necesita esto, o está haciendo algo mal, o tiene un mejor argumento para su necesidad que la "mejor práctica". Sí, es una práctica estándar, pero no para la optimización genérica del rendimiento, sino solo para la microoptimización.
Bergi
3
Kiss se aplica en todas partes. La optimización prematura es una anti-práctica.
Bergi
7

Como otros han dicho, no deberías usar for..in para iterar sobre una matriz.

for ( var i = 0, len = myArray.length; i < len; i++ ) { ... }

Si desea una sintaxis más limpia, puede usar forEach:

myArray.forEach( function ( val, i ) { ... } );

Si desea utilizar este método, asegúrese de incluir la cuña ES5 para agregar compatibilidad con navegadores más antiguos.

Robert Messerle
fuente
2

Respuesta dada por rushUp es correcta pero esto será más conveniente

for (let [index, val] of array.entries() || []) {
   // your code goes here    
}
Renote Gotecha
fuente
1

Aquí hay una función eachWithIndexque funciona con cualquier cosa iterable.

También podría escribir una función similar eachWithKeyque funcione con objetos usando for...in.

// example generator (returns an iterator that can only be iterated once)
function* eachFromTo(start, end) { for (let i = start; i <= end; i++) yield i }

// convers an iterable to an array (potential infinite loop)
function eachToArray(iterable) {
    const result = []
    for (const val of iterable) result.push(val)
    return result
}

// yields every value and index of an iterable (array, generator, ...)
function* eachWithIndex(iterable) {
    const shared = new Array(2)
    shared[1] = 0
    for (shared[0] of iterable) {
        yield shared
        shared[1]++
    }
}

console.log('iterate values and indexes from a generator')
for (const [val, i] of eachWithIndex(eachFromTo(10, 13))) console.log(val, i)

console.log('create an array')
const anArray = eachToArray(eachFromTo(10, 13))
console.log(anArray)

console.log('iterate values and indexes from an array')
for (const [val, i] of eachWithIndex(anArray)) console.log(val, i)

Lo bueno de los generadores es que son flojos y pueden tomar el resultado de otro generador como argumento.

Rivenfall
fuente
1

Esa es mi versión de un iterador compuesto que produce un índice y cualquier valor pasado de la función del generador con un ejemplo de búsqueda primaria (lenta):

const eachWithIndex = (iterable) => {
  return {
    *[Symbol.iterator]() {
      let i = 0
      for(let val of iteratable) {
        i++
          yield [i, val]
      }
    }
  }

}

const isPrime = (n) => {
  for (i = 2; i < Math.floor(Math.sqrt(n) + 1); i++) {
    if (n % i == 0) {
      return false
    }
  }
  return true
}

let primes = {
  *[Symbol.iterator]() {
    let candidate = 2
    while (true) {
      if (isPrime(candidate)) yield candidate
        candidate++
    }
  }
}

for (const [i, prime] of eachWithIndex(primes)) {
  console.log(i, prime)
  if (i === 100) break
}

akurtser
fuente
¿Por qué tienes una función en eachWithIndex[Symbol.iterator]lugar de solo una función eachWithIndex? eachWithIndexno satisface la interfaz iterable, que es el objetivo de Symbol.iterator.
Ry-
@ Ry- Buena captura, cambiada eachWithIndexpara aceptar iterable y devolver un iterativo compuesto cerrado.
akurtser
1

Además de las muy buenas respuestas que todos publicaron, quiero agregar que la solución más eficiente es el ES6 entries. Parece contradictorio para muchos desarrolladores aquí, así que creé este perf benchamrk .

ingrese la descripción de la imagen aquí

Es ~ 6 veces más rápido. Principalmente porque no necesita: a) acceder a la matriz más de una vez y, b) emitir el índice.

sospedra
fuente