Отримайте цикл / індекс циклу, використовуючи для… синтаксису в JavaScript


317

Обережно:

питання все ще стосується 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

6
Не використовуйте для ... in для масивів. І в будь-якому випадку, воно повторює імена властивостей, а не значення властивостей.
Фелікс Клінг

1
Це масив, а не об’єкт, правда? Отже alert(obj),?
Ракета Hazmat

Відповіді:


545

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включає численні властивості в будь-якому місці ланцюга прототипу.


2
О, добре. Я розгубився. Я вважав, що вхід JavaScript у JavaScript такий самий, як у Python. Дякуємо за роз’яснення.
hobbes3

1
@quantumpotato: lets є varблоком області. consts незмінні.
Ри-

1
це була детальна відповідь, дякую за це. Дійсно прояснили всі обговорювані речі
Дехерай Бхаскар

1
дурне запитання, але на чому насправді стоїть% d і% s, чи вони можуть бути будь-якою буквою, якою я їх хочу?
klewis

2
@klewis: %dформатує ціле число і %sформатує рядок. Вони засновані на printf . Спеціалізація працює на console.spec.whatwg.org/#formatter .
Ри-

162

У ES6 добре використовувати для - циклу. Ви можете отримати індекс для подібного

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

Зауважте, що Array.entries()повертає ітератор , саме це дозволяє йому працювати в циклі for-of; не плутайте це з Object.entries () , який повертає масив пар ключ-значення.


9
Це набагато краща відповідь, ніж прийнята!
trusktr

3
Я думаю, що це рішення краще, ніж forEach ... Він використовує номінал для ... синтаксису циклу, і вам не потрібно використовувати окрему функцію. Іншими словами, це синтаксично краще. ОП, здається, цього хотів.
u8y7541

1
entries()повертається порожній об'єкт: {}. Будь-яка ідея, чому це було б? Мій array- це масив об'єктів.
Джошуа Пінтер

@JoshuaPinter спробуйте Object.entries(array)замістьarray.entries()
tonyg

2
Це повинен зробити так, Джошуа - об'єкт є ітератором, об'єктом з next()методом, який повертає наступні записи в масиві щоразу, коли він викликається. У ньому немає (видимих) даних; Ви отримуєте дані в базовому об'єкті, зателефонувавши next(), що для-з-за кадрів. cc @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

19

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

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

arr - ARRAY, obj - KEY поточного елемента, i - COUNTER / INDEX

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


7
Я б запропонував: замість цього використовуйте лічильник, збільшуючи його в циклі.
mayankcpdixit

2
Додавши до mayankcpdixit, замість цього використовуйте лічильник, тому що indexOf може мати негативний вплив на продуктивність.
Дін Лю

1
Чим більший об’єкт, тим повільніше це стане. Це не масштабується.
dchacke

2
Це свого роду безцільно повільним і складним , тому що var i = 0;і i++;коротше і ефективніше. Плюс це не працює для численних властивостей, які не є власними властивостями.
Ри-

1
@trusktr: І якщо це потрібно ... ви все одно не повинні цим користуватися. Просто змінюйте лічильник, коли ви змінюєте колекцію. Якщо воно не повинно бути на місці, замість цього зробіть приємну функціональну трансформацію.
Ри-

13

Для циклу ітерація властивостей об'єкта. Не використовуйте їх для масивів, навіть якщо вони іноді працюють.

Властивості об'єктів тоді не мають індексу, всі вони рівні і не потрібно проходити у визначеному порядку. Якщо ви хочете порахувати властивості, вам доведеться встановити додатковий лічильник (як це було в першому прикладі).

петля над масивом:

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++)
Фелікс Санц,

16
@FelixSanz: Відходи? У жодному разі. Це передчасна мікрооптимізація, яка навряд чи потрібна, і var i=0; i<a.length; i++)це стандартний зразок циклу, який у будь-якому разі оптимізується кожним гідним механізмом JavaScript.
Бергі

3
@FelixSanz: Так, і var i=0; i<a.length; i++це найкраща практика.
Бергі

1
KISS . Якщо ви пишете цикли, де вам це справді потрібно, ви або робите щось не так, або у вас є кращий аргумент щодо його необхідності, ніж "найкраща практика". Так, це стандартна практика, але не для загальної оптимізації продуктивності, а лише для мікрооптимізації.
Бергі

3
KISS застосовується скрізь. Передчасна оптимізація - це антипрактика.
Бергі

7

Як вже говорили інші, вам не слід використовувати for..in для ітерації через масив.

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

Якщо ви хочете більш чистий синтаксис, ви можете використовувати дляEach:

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)

Хороша річ із генераторами - це те, що вони ліниві і можуть сприйняти результат іншого генератора як аргумент.


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
}


Чому у вас функція eachWithIndex[Symbol.iterator]замість просто функції eachWithIndex? eachWithIndexне задовольняє ітерабельний інтерфейс, у чому вся суть Symbol.iterator.
Ри-

@ Ry - Хороший улов, змінений eachWithIndexна прийняття ітерабельного та повернення закритого композитного ітерабельного.
акурцер

1

На додаток до дуже хороших відповідей, які кожен розмістив, хочу додати, що найефективнішим рішенням є ES6 entries. Для багатьох дияволів це здається протизаконним, тому я створив цей парфум-бенчмарк .

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

Це ~ в 6 разів швидше. Головним чином, тому, що не потрібно: а) отримувати доступ до масиву не один раз і б) вносити індекс.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.