Pobierz licznik / indeks pętli za pomocą… składni w JavaScript

317

Uwaga:

pytanie nadal dotyczy for…ofpętli.> Nie używaj for…indo iteracji po tablicy , użyj go do iteracji po właściwościach obiektu. To powiedziawszy to


Rozumiem, że podstawowa for…inskładnia w JavaScript wygląda następująco:

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

Ale jak uzyskać licznik / indeks pętli ?

Wiem, że prawdopodobnie mógłbym zrobić coś takiego:

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

Lub nawet stary dobry:

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

Wolałbym jednak użyć prostszej for-inpętli. Myślę, że wyglądają lepiej i mają więcej sensu.

Czy istnieje prostszy lub bardziej elegancki sposób?


W Pythonie jest to łatwe:

for i, obj in enumerate(myArray):
    print i
hobbes3
źródło
6
Nie używaj dla ... w przypadku tablic. W każdym razie iteruje nazwy właściwości, a nie wartości właściwości.
Felix Kling
1
To tablica, a nie obiekt, prawda? A więc alert(obj)?
Rocket Hazmat

Odpowiedzi:

545

for…initeruje nazwy właściwości, a nie wartości, i robi to w nieokreślonej kolejności (tak, nawet po ES6). Nie należy go używać do iteracji po tablicach. Dla nich istnieje forEachmetoda ES5, która przekazuje zarówno wartość, jak i indeks do funkcji, którą dajesz:

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

Lub ES6 Array.prototype.entries, który ma teraz obsługę we wszystkich wersjach przeglądarki:

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

Jednak w przypadku iteracji (gdzie for…ofzamiast pętli użyłbyś pętli for…in), nie ma nic wbudowanego:

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

próbny

Jeśli naprawdę miałeś na myśli for…in- wyliczanie właściwości - potrzebujesz dodatkowego licznika. Object.keys(obj).forEachmoże działać, ale obejmuje tylko własne właściwości; for…inzawiera wyliczalne właściwości w dowolnym miejscu łańcucha prototypu.

Ry-
źródło
2
Oh ok Byłem zmieszany. Myślałem, że for-in JavaScript jest taki sam jak Python. Dziękuję za wyjaśnienie.
hobbes3
1
@quantumpotato: lets są varo zasięgu blokowym. consts są niezmienne.
Ry-
1
to była szczegółowa odpowiedź, dzięki za to. Naprawdę wyjaśniono wszystkie omawiane rzeczy
Dheeraj Bhaskar,
1
głupie pytanie, ale co właściwie oznacza% d i% s, czy może to być jakikolwiek list, jaki chcę, żeby był?
klewis
2
@klewis: %dformatuje liczbę całkowitą i %sformatuje ciąg. Są oparte na printf . Specyfikacja jest w trakcie tworzenia na console.spec.whatwg.org/#formatter .
Ry-
162

W ES6 dobrze jest używać pętli -. Możesz uzyskać indeks dla tego typu

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

Zauważ, że Array.entries()zwraca iterator , który pozwala mu pracować w pętli for-of; nie myl tego z Object.entries () , która zwraca tablicę par klucz-wartość.

spieszyć się
źródło
9
To znacznie lepsza odpowiedź niż zaakceptowana!
trusktr
3
Myślę, że to rozwiązanie jest lepsze niż forEach ... Używa nomral dla ... składni pętli i nie musisz używać osobnej funkcji. Innymi słowy, jest lepszy składniowo. Wygląda na to, że PO tego chciał.
u8y7541
1
entries()wraca pusty obiekt: {}. Masz pomysł, dlaczego tak jest? My arrayis Array of Objects.
Joshua Pinter
@JoshuaPinter spróbuj Object.entries(array)zamiastarray.entries()
tonyg
2
Ma to zrobić, Joshua - obiekt jest iteratorem, obiektem z next()metodą, która zwróci kolejne wpisy w tablicy za każdym razem, gdy zostanie wywołane. Nie ma w nim (widocznych) danych; dostajesz dane w obiekcie bazowym, wywołując next(), co robi za kulisami. cc @tonyg
Shog9,
26

Co powiesz na to

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

Gdzie array.forEachta metoda ma indexparametr będący indeksem bieżącego elementu przetwarzanego w tablicy.

Sanjay Shr
źródło
1
najlepsza odpowiedź tutaj
codepleb
4
Wybrana odpowiedź została opublikowana 6 lat przed tym i ma już to samo ...
Deiv 16.04.19
Foreach nie jest dobry do optymalizacji, ponieważ breaknie jest dostępny.
smartworld-dm
19

Rozwiązanie dla małych kolekcji tablic:

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

arr - ARRAY, obj - KLUCZ bieżącego elementu, i - LICZNIK / INDEKS

Uwaga: Metoda keys () nie jest dostępna dla wersji IE <9, powinieneś użyć kodu Polyfill . https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

iwasborntobleed
źródło
7
Sugeruję: użyj zamiast tego licznika, zwiększaj go w pętli.
mayankcpdixit
2
Dodając do mayankcpdixit, użyj zamiast tego licznika, ponieważ indexOf może mieć negatywny wpływ na wydajność.
Dean Liu,
1
Im większy obiekt, tym wolniej będzie. To nie jest skalowane.
dchacke
2
Jest to rodzaj niepotrzebnie powolny i skomplikowany, bo var i = 0;i i++;jest krótsza i bardziej wydajne. Ponadto nie działa w przypadku wyliczalnych właściwości, które nie są własnymi właściwościami.
Ry-
1
@trusktr: A jeśli jest to wymagane… nadal nie powinieneś tego używać. Po prostu zmień licznik po zmianie kolekcji. Jeśli nie musi być na miejscu, zamiast tego wykonaj ładną transformację funkcjonalną.
Ry-
13

Pętle dla pętli iterują właściwości obiektu. Nie używaj ich do tablic, nawet jeśli czasami działają.

Właściwości obiektu nie mają wówczas indeksu, wszystkie są równe i nie trzeba ich przeglądać w określonej kolejności. Jeśli chcesz policzyć właściwości, będziesz musiał ustawić dodatkowy licznik (tak jak w pierwszym przykładzie).

zapętlić tablicę:

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

zapętlić obiekt:

var o = {};
for (var prop in o) {
    prop // is the property name
    o[prop] // is the property value - the item
}
Bergi
źródło
3
Nigdy nie rób (var i=0; i<a.length; i++)tak , jak zmarnowane zasoby. Użyj(var i=0, var len = a.length; i<len; i++)
Félix Sanz
16
@FelixSanz: marnotrawstwo zasobów? Nie ma mowy. Jest to przedwczesna mikrooptymalizacja, która prawie nigdy nie jest konieczna, i var i=0; i<a.length; i++)jest to standardowy wzorzec pętli, który jest optymalizowany przez każdy przyzwoity silnik javascript.
Bergi,
3
@FelixSanz: Tak i var i=0; i<a.length; i++jest najlepszą praktyką.
Bergi,
1
KISS . Jeśli piszesz pętle tam, gdzie naprawdę tego potrzebujesz, albo robisz coś złego, albo masz lepszy argument za jego koniecznością niż „najlepsza praktyka”. Tak, jest to standardowa praktyka, ale nie do ogólnej optymalizacji wydajności, ale tylko do mikrooptymalizacji.
Bergi,
3
KISS obowiązuje wszędzie. Przedwczesna optymalizacja jest antypraktyką.
Bergi,
7

Jak powiedzieli inni, nie powinieneś używać for..in do iteracji po tablicy.

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

Jeśli chcesz czystszej składni, możesz użyć forEach:

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

Jeśli chcesz skorzystać z tej metody, upewnij się, że dołączasz podkładkę ES5, aby dodać obsługę starszych przeglądarek.

Robert Messerle
źródło
2

Odpowiedź udzielona przez rushUp jest poprawna, ale będzie to wygodniejsze

for (let [index, val] of array.entries() || []) {
   // your code goes here    
}
Renish Gotecha
źródło
1

Oto funkcja, eachWithIndexktóra działa ze wszystkim, co można powtórzyć.

Możesz także napisać podobną funkcję eachWithKey która działa z użyciem objets 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)

Zaletą generatorów jest to, że są leniwi i mogą brać argument innego generatora jako argument.

Rivenfall
źródło
1

To jest moja wersja iteratora złożonego, która daje indeks i wartość przekazanej funkcji generatora z przykładem (wolnego) wyszukiwania pierwszego:

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
źródło
Dlaczego masz funkcję eachWithIndex[Symbol.iterator]zamiast samej funkcji eachWithIndex? eachWithIndexnie spełnia iterowalnego interfejsu, o co w tym wszystkim chodzi Symbol.iterator.
Ry-
@ Ry- Dobry haczyk, zmieniono, eachWithIndexaby zaakceptować iterowalny i zwrócić zamknięty iterowalny kompozyt.
akurtser
1

Oprócz bardzo dobrych odpowiedzi, które wszyscy napisali, chcę dodać, że najbardziej wydajnym rozwiązaniem jest ES6 entries. Wydaje się, że dla wielu deweloperów jest to kontrowersyjne , więc stworzyłem ten doskonały benchmark .

wprowadź opis zdjęcia tutaj

Jest ~ 6 razy szybszy. Głównie dlatego, że nie musi: a) uzyskiwać dostępu do tablicy więcej niż raz i b) rzutować indeks.

sospedra
źródło