Obter contador / índice de loop usando a sintaxe for… em JavaScript

318

Cuidado:

a pergunta ainda se aplica aos for…ofloops.> Não use for…inpara iterar sobre uma matriz , use-o para iterar sobre as propriedades de um objeto. Dito isto, este


Entendo que a for…insintaxe básica do JavaScript se parece com isso:

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

Mas como obtenho o contador / índice de loop ?

Eu sei que provavelmente poderia fazer algo como:

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

Ou até o bom e velho:

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

Mas eu prefiro usar o for-inloop mais simples . Eu acho que eles parecem melhores e fazem mais sentido.

Existe uma maneira mais simples ou mais elegante?


No Python, é fácil:

for i, obj in enumerate(myArray):
    print i
hobbes3
fonte
6
Não use para ... in para matrizes. De qualquer forma, ele repete os nomes das propriedades, não os valores das propriedades.
Felix Kling
1
É uma matriz, não um objeto, certo? Então alert(obj)?
Rocket Hazmat

Respostas:

546

for…initera sobre nomes de propriedades, não valores, e faz isso em uma ordem não especificada (sim, mesmo depois do ES6). Você não deve usá-lo para iterar sobre matrizes. Para eles, existe o forEachmétodo do ES5 que passa o valor e o índice para a função que você atribui:

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

Ou ES6 Array.prototype.entries, que agora tem suporte nas versões atuais do navegador:

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

Para iterables em geral (onde você usaria um for…ofloop em vez de a for…in), no entanto, não há nada embutido:

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);
}

demonstração

Se você realmente quis dizer for…in- enumerando propriedades - seria necessário um contador adicional. Object.keys(obj).forEachpoderia funcionar, mas inclui apenas propriedades próprias ; for…ininclui propriedades enumeráveis ​​em qualquer lugar da cadeia de protótipos.

Ry-
fonte
2
Ah ok. Eu estava confuso. Eu pensei que o for-in do JavaScript fosse o mesmo do Python. Obrigado pelo esclarecimento.
precisa saber é o seguinte
1
@quantumpotato: lets são vars com escopo de bloco. consts são imutáveis.
Ry-
1
essa foi uma resposta detalhada, obrigado por isso. Realmente esclarecidas todas as coisas discutidas
Dheeraj Bhaskar
1
pergunta estúpida, mas o que% d e% s realmente representam, ou poderiam ser qualquer letra que eu queira que sejam?
precisa saber é
2
@klewis: %dformata um número inteiro e %sformata uma string. Eles são baseados em printf . Uma especificação está em andamento em console.spec.whatwg.org/#formatter .
Ry-
163

No ES6, é bom usar o loop for - of. Você pode obter um índice como este

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

Observe que Array.entries()retorna um iterador , que é o que permite que ele funcione no loop for-of; não confunda isso com Object.entries () , que retorna uma matriz de pares de valores-chave.

rushUp
fonte
9
Esta é uma resposta muito melhor do que a aceita!
trusktr
3
Acho que esta solução é melhor que a forEach ... Usa o nomral para ... da sintaxe do loop, e você não precisa usar uma função separada. Em outras palavras, é sintaticamente melhor. O OP parece ter desejado isso.
u8y7541
1
entries()é um objecto retornar vazio: {}. Alguma idéia de por que isso seria? My arrayé uma matriz de objetos.
30717 Joshua Pinter
@JoshuaPinter tente em Object.entries(array)vez dearray.entries()
tonyg
2
Ele deveria fazer isso, Joshua - o objeto é um iterador, um objeto com um next()método que retornará entradas subsequentes na matriz toda vez que for chamado. Não há dados (visíveis) nele; você obtém os dados no objeto subjacente chamando next(), o que ocorre por trás dos bastidores. cc @tonyg
Shog9
26

Que tal agora

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

Onde array.forEacheste método possui um indexparâmetro que é o índice do elemento atual sendo processado na matriz.

Sanjay Shr
fonte
1
melhor resposta aqui
codepleb
4
A resposta escolhida foi publicado 6 anos antes deste e tem a mesma coisa já nele ...
Deiv
O Foreach não é bom para otimização, pois breaknão está disponível.
smartworld-dm
20

Solução para pequenas coleções de matrizes:

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

arr - ARRAY, obj - CHAVE do elemento atual, i - COUNTER / INDEX

Aviso: Teclas de método () não estão disponíveis para a versão IE <9, você deve usar o código Polyfill . https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

iwasborntobleed
fonte
7
Eu sugiro: use um contador, aumente-o em loop.
Mayankcpdixit
2
Adicionando ao mayankcpdixit, use um contador, pois o indexOf pode ter um impacto negativo no desempenho.
Dean Liu
1
Quanto maior o objeto, mais lento ele ficará. Isso não escala.
precisa saber é o seguinte
2
Este é tipo de inutilmente lento e complicado, porque var i = 0;e i++;é mais curto e mais eficiente. Além disso, ele não funciona para propriedades enumeráveis ​​que não são propriedades próprias.
Ry-
1
@trusktr: E se for necessário ... você ainda não deve usar isso. Apenas altere o contador quando alterar a coleção. Se não precisar estar no local, faça uma boa transformação funcional.
Ry-
13

Os loops de entrada iteram sobre as propriedades de um objeto. Não os use para matrizes, mesmo que elas funcionem algumas vezes.

As propriedades do objeto não têm índice, são todas iguais e não precisam ser executadas em uma ordem determinada. Se você quiser contar propriedades, precisará configurar o contador extra (como fez no seu primeiro exemplo).

loop sobre uma matriz:

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

loop sobre um objeto:

var o = {};
for (var prop in o) {
    prop // is the property name
    o[prop] // is the property value - the item
}
Bergi
fonte
3
Nunca faça (var i=0; i<a.length; i++)como é desperdiçado recursos. Use(var i=0, var len = a.length; i<len; i++)
Félix Sanz
16
@FelixSanz: Desperdiçar recursos? De jeito nenhum. Essa é uma micro-otimização prematura que quase nunca é necessária, e var i=0; i<a.length; i++)é o padrão de loop padrão que é otimizado por qualquer mecanismo javascript decente de qualquer maneira.
Bergi 29/08/14
3
@FelixSanz: Sim, e var i=0; i<a.length; i++é a melhor prática.
Bergi
1
BEIJO . Se você escreve loops onde realmente precisa disso, está fazendo algo errado ou tem um argumento melhor para a necessidade do que "melhores práticas". Sim, é uma prática padrão, mas não para otimização de desempenho genérica, mas apenas para micro-otimização.
Bergi
3
O KISS se aplica a todos os lugares. A otimização prematura é uma anti-prática.
Bergi
7

Como outros já disseram, você não deve usar for..in para iterar sobre uma matriz.

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

Se você deseja uma sintaxe mais limpa, pode usar o forEach:

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

Se você deseja usar esse método, inclua o calço ES5 para adicionar suporte a navegadores mais antigos.

Robert Messerle
fonte
2

Resposta dada por rushUp Está correto, mas isso será mais conveniente

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

Aqui está uma função eachWithIndex que funciona com qualquer coisa iterável.

Você também pode escrever uma função semelhante eachWithKeyque funcione com 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)

O bom dos geradores é que eles são preguiçosos e podem aceitar o resultado de outro gerador como argumento.

Rivenfall
fonte
1

Essa é a minha versão de um iterador composto que gera um índice e o valor de qualquer função de gerador passada com um exemplo de pesquisa principal (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
fonte
Por que você tem uma função em eachWithIndex[Symbol.iterator]vez de apenas uma função eachWithIndex? eachWithIndexnão satisfaz a interface iterável, que é o ponto principal Symbol.iterator.
Ry-
@ Ry- Boa captura, alterada eachWithIndexpara aceitar iterável e retornar um composto fechado iterável.
akurtser
1

Além das respostas muito boas que todos postaram, quero acrescentar que a solução com melhor desempenho é o ES6 entries. Parece contra- intuitivo para muitos desenvolvedores aqui, então eu criei esse perf benchamrk .

insira a descrição da imagem aqui

É ~ 6 vezes mais rápido. Principalmente porque não precisa: a) acessar a matriz mais de uma vez e b) converter o índice.

sospedra
fonte