Получить счетчик / индекс цикла, используя для… синтаксиса в JavaScript

318

Внимание:

Вопрос по-прежнему относится к for…ofциклам.> Не используйте for…inдля итерации по массиву , используйте его для итерации по свойствам объекта. Тем не менее, это


Я понимаю, что основной for…inсинтаксис в JavaScript выглядит так:

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

Но как мне получить счетчик / индекс цикла ?

Я знаю, что мог бы сделать что-то вроде:

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

Или даже старый добрый

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

Но я бы предпочел использовать более простой for-inцикл. Я думаю, что они выглядят лучше и имеют больше смысла.

Есть ли более простой или более элегантный способ?


В Python это просто:

for i, obj in enumerate(myArray):
    print i
hobbes3
источник
6
Не используйте для ... в массивах. И в любом случае, он перебирает имена свойств, а не значения свойств.
Феликс Клинг
1
Это массив, а не объект, верно? Так alert(obj)?
Ракетный Хазмат

Ответы:

548

for…inперебирает имена свойств, а не значения, и делает это в неопределенном порядке (да, даже после ES6). Вы не должны использовать его для перебора массивов. Для них есть forEachметод ES5, который передает значение и индекс функции, которую вы ему передаете:

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

Или ES6 Array.prototype.entries, которые теперь поддерживают текущие версии браузеров:

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

Однако для итерируемых элементов вообще (где вы бы использовали for…ofцикл вместо a for…in) ничего встроенного нет:

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

демонстрация

Если вы действительно имели в виду for…in- перечисление свойств - вам понадобится дополнительный счетчик. Object.keys(obj).forEachможет работать, но включает только собственные свойства; for…inвключает перечисляемые свойства в любом месте цепочки прототипов.

Ry-
источник
2
Ох хорошо Я обескуражен. Я думал, что JavaScript в for-in был таким же, как Python. Спасибо за разъяснения.
hobbes3
1
@quantumpotato: lets varс областью блока. constс неизменны.
Ry-
1
это был подробный ответ, спасибо за это. Действительно разъяснил все обсуждаемые вещи
Дирадж Бхаскар
1
глупый вопрос, но что на самом деле означают% d и% s, или это может быть любая буква, которую я хочу, чтобы они были?
Klewis
2
@klewis: %dформатирует целое число и %sформатирует строку. Они основаны на printf . Спецификация выполняется в console.spec.whatwg.org/#formatter .
Ry-
163

В ES6 это хорошо использовать для - цикла. Вы можете получить индекс для как это

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

Обратите внимание, что Array.entries()возвращается итератор , который позволяет ему работать в цикле for; не путайте это с Object.entries () , который возвращает массив пар ключ-значение.

бросаться
источник
9
Это гораздо лучший ответ, чем принятый!
trusktr
3
Я думаю, что это решение лучше, чем forEach ... Оно использует номинал для ... синтаксиса цикла, и вам не нужно использовать отдельную функцию. Другими словами, это синтаксически лучше. ОП, похоже, хотел этого.
u8y7541
1
entries()возвращается пустой объект: {}. Есть идеи, почему это будет? Моя arrayэто массив объектов.
Джошуа Пинтер
@JoshuaPinter попробуйте Object.entries(array)вместоarray.entries()
Tonyg
2
Это должно быть сделано, Джошуа - объект является итератором, объектом с next()методом, который будет возвращать последующие записи в массиве каждый раз, когда он вызывается. Там нет (видимых) данных в нем; Вы получаете данные в базовом объекте путем вызова next(), что for-of делает за кулисами. куб.см @tonyg
Shog9
26

Как насчет этого

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

Где array.forEachэтот метод имеет indexпараметр, который является индексом текущего элемента, обрабатываемого в массиве.

Санджай Шр
источник
1
лучший ответ здесь
codepleb
4
Выбранный ответ был опубликован за 6 лет до этого и уже содержит то же самое ...
Deiv
Foreach не подходит для оптимизации, так breakкак недоступен.
smartworld-dm
20

Решение для небольших коллекций массивов:

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

arr - ARRAY, obj - ключ текущего элемента, i - COUNTER / INDEX

Примечание: Ключ метода () недоступен для IE версии <9, вы должны использовать код Polyfill . https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

iwasborntobleed
источник
7
Я бы предложил: используйте счетчик, увеличивайте его в цикле.
Mayankcpdixit
2
В дополнение к mayankcpdixit, используйте вместо этого счетчик, потому что indexOf может оказать негативное влияние на производительность.
Дин Лю
1
Чем больше объект, тем медленнее это будет. Это не масштабируется.
Weltschmerz
2
Это своего рода бесцельно медленным и сложным , потому что var i = 0;и i++;короче и эффективнее. Плюс это не работает для перечисляемых свойств, которые не являются собственными свойствами.
Ry-
1
@trusktr: И если это требуется ... вы все равно не должны использовать это. Просто измените счетчик, когда вы измените коллекцию. Если это не должно быть на месте, вместо этого сделайте хорошее функциональное преобразование.
Ry-
13

Циклы for-in-loop перебирают свойства объекта. Не используйте их для массивов, даже если они иногда работают.

Свойства объекта в этом случае не имеют индекса, все они равны и не должны просматриваться в определенном порядке. Если вы хотите посчитать свойства, вам нужно установить дополнительный счетчик (как вы это делали в первом примере).

цикл по массиву:

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

цикл по объекту:

var o = {};
for (var prop in o) {
    prop // is the property name
    o[prop] // is the property value - the item
}
Берги
источник
3
Никогда не делайте так, (var i=0; i<a.length; i++)как потраченные впустую ресурсы. Использование(var i=0, var len = a.length; i<len; i++)
Félix Sanz
16
@FelixSanz: тратить ресурсы? Ни за что. Это преждевременная микрооптимизация, которая вряд ли когда-либо понадобится, и var i=0; i<a.length; i++)это стандартный шаблон цикла, который в любом случае оптимизируется каждым приличным движком javascript.
Берги
3
@FelixSanz: Да, и var i=0; i<a.length; i++это лучшая практика.
Берги
1
ПОЦЕЛУЙ . Если вы пишете циклы там, где это действительно нужно, вы либо делаете что-то не так, либо у вас есть более веский аргумент в пользу его необходимости, чем «передовой опыт». Да, это стандартная практика, но не для общей оптимизации производительности, а только для микрооптимизации.
Берги
3
ПОЦЕЛУЙ применяется везде. Преждевременная оптимизация - это антипрактика.
Берги
7

Как уже говорили другие, вы не должны использовать for..in для перебора массива.

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

Если вы хотите более чистый синтаксис, вы можете использовать forEach:

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

Если вы хотите использовать этот метод, убедитесь, что вы включили шайбу ES5 для добавления поддержки старых браузеров.

Роберт Мессерле
источник
2

Ответ от rushUp верен, но это будет удобнее

for (let [index, val] of array.entries() || []) {
   // your code goes here    
}
Рениш Готеча
источник
1

Вот функция, eachWithIndexкоторая работает с чем угодно итеративным.

Вы также можете написать аналогичную функцию, eachWithKeyкоторая работает с использованием объектов 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)

Хорошая вещь с генераторами - то, что они ленивы и могут взять результат другого генератора в качестве аргумента.

Rivenfall
источник
1

Это моя версия составного итератора, который возвращает индекс и значение любой переданной функции генератора с примером (медленного) простого поиска:

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
источник
Почему у вас есть функция, eachWithIndex[Symbol.iterator]а не просто функция eachWithIndex? eachWithIndexне удовлетворяет итерируемый интерфейс, в чем и заключается весь смысл Symbol.iterator.
Ry-
@ Ry- Хороший улов, изменен eachWithIndexдля принятия итерируемого и возврата закрытого составного итерируемого.
Аккурсер
1

В дополнение к очень хорошим ответам, которые все опубликовали, я хочу добавить, что наиболее эффективным решением является ES6 entries. Для многих разработчиков это кажется нелогичным , поэтому я создал этот тестовый стенд .

введите описание изображения здесь

Это ~ в 6 раз быстрее. Главным образом потому, что не нужно: а) обращаться к массиву более одного раза и б) приводить индекс.

sospedra
источник