Menu
Русский

for...of и for...in в JavaScript: перебор массивов и объектов

Разбираемся, чем на самом деле отличаются for...of и for...in в JavaScript: значения против ключей, массивы против объектов и когда какой цикл уместнее.

Два цикла — две разные задачи

В JavaScript есть две конструкции цикла, которые выглядят почти одинаково, но ведут себя совершенно по-разному. for...of перебирает значения итерируемой сущности. for...in перебирает ключи объекта. Разница — в одной букве, а поведение отличается кардинально.

Вот вся суть на одном примере:

index.js
Output
Click Run to see the output here.

Первый цикл выведет apple, banana, cherry — то есть значения. Второй — 0, 1, 2, ключи в виде строк. Перепутаете — и потом минут десять будете гадать, откуда в массиве взялись числа.

Цикл for of в JavaScript: значения из всего, что итерируемо

for...of — это тот цикл, к которому вы будете тянуться чаще всего. Он работает со всем, что в JavaScript считается итерируемым: массивы, строки, Map, Set, NodeList, генераторы и не только.

index.js
Output
Click Run to see the output here.

Никакой возни с индексами, никаких scores[i]. Просишь значения — получаешь значения. Строки тоже итерируемы, так что for...of пройдётся по ним посимвольно:

index.js
Output
Click Run to see the output here.

Такой подход корректно работает и с большинством код-поинтов Unicode — мелочь, но это реальный плюс по сравнению с обращением к строке по числовому индексу.

Как получить индекс в for of

Единственное, чего for...of не даёт из коробки, — это позиция элемента. Если индекс всё-таки нужен, используйте цикл вместе с entries():

index.js
Output
Click Run to see the output here.

names.entries() отдаёт пары [индекс, значение], а деструктуризация слева раскладывает их по двум переменным. Обычно это выглядит аккуратнее, чем возвращаться к классическому for (let i = 0; ...) только ради того, чтобы получить i.

for...in: перебор ключей объекта

Цикл for...in в JavaScript создан как раз для обычных объектов. Он проходит по перечисляемым строковым ключам:

index.js
Output
Click Run to see the output here.

Обрати внимание: в переменную попадает ключ, а не значение. Чтобы получить значение, обращаемся к объекту по индексу — user[key]. И да, каждый ключ — это строка, даже если визуально это число.

Ещё один нюанс: for...in проходит и по цепочке прототипов, то есть в переборе могут всплыть унаследованные свойства. Для обычных литеральных объектов это редко создаёт проблемы, но если вы обходите экземпляры класса или объекты из сторонней библиотеки, лучше подстраховаться:

index.js
Output
Click Run to see the output here.

Object.hasOwn(user, key) отсекает всё, что пришло из прототипа. В современном коде for...in обычно вообще не используют — вместо него берут Object.keys, Object.values или Object.entries. Об этом как раз следующий раздел.

Современный способ перебора объекта в JavaScript

Вместо for...in связку for...of с одним из помощников Object.*. Какой из трёх брать — зависит от того, что именно вам нужно:

index.js
Output
Click Run to see the output here.

Object.entries — вообще находка: деструктуризация пары [key, value] читается буквально как обычный текст. Плюс эти методы отдают только собственные перечисляемые свойства объекта, так что про унаследованный мусор можно не думать.

Почему for...in не стоит использовать для массивов

Формально это не запрещено, но подвох тут подстерегает многих:

index.js
Output
Click Run to see the output here.

Вы получите 0, 1, 2и ещё tag. Любое свойство, которое кто-то навесил на массив (или на Array.prototype через полифилл), тоже попадёт в перебор. Ключи — это строки, поэтому key + 1 даст конкатенацию, а не сложение. Да и порядок обхода в некоторых крайних случаях не гарантирован совпадающим с порядком элементов массива.

Простое правило:

  • Нужны значения массива? for...of arr.
  • Нужны и индекс, и значение? for...of arr.entries().
  • Нужен только индекс или обычный счётчик? Классический for (let i = 0; i < arr.length; i++).
  • Ключи или пары ключ-значение объекта? Object.keys(obj) / Object.entries(obj) вместе с for...of.

Короче говоря: for...in — инструмент на любителя для узких задач. Связки for...of и методов Object хватает практически всегда.

break и continue работают в обоих циклах

Оба цикла поддерживают привычные способы досрочного выхода:

index.js
Output
Click Run to see the output here.

continue переходит к следующей итерации, а break полностью выходит из цикла. Именно в этом главное преимущество for...of перед .forEach(): из колбэка forEach невозможно выйти через break, а из for...of — пожалуйста.

Сравнение бок о бок

index.js
Output
Click Run to see the output here.

Четыре цикла, четыре задачи, одна простая модель: нужны значения из итерируемой сущности — бери for...of. Работаешь со свойствами объекта — иди через Object.keys / Object.values / Object.entries, и снова через for...of. А for...in оставь на тот редкий случай, когда тебе действительно нужны все перечисляемые строковые ключи объекта, включая унаследованные.

Что дальше: truthy и falsy значения

Любой цикл и любой if в JavaScript рано или поздно задают один и тот же вопрос: считается ли это значение истинным? И ответ далеко не всегда очевиден — пустая строка, ноль, null и undefined ведут себя совсем не так, как можно было бы подумать. Дальше разберём truthy и falsy значения.

Часто задаваемые вопросы

В чём разница между for...of и for...in в JavaScript?

for...of проходит по значениям итерируемых сущностей — массивов, строк, Map, Set. for...in перебирает ключи (имена свойств) объекта. Для массива ['a', 'b'] for...of вернёт 'a' и 'b', а for...in'0' и '1' в виде строк.

Можно ли использовать for...of для обычного объекта?

Напрямую — нет, обычные объекты не итерируемые. Сначала нужно получить массив через Object.keys(obj), Object.values(obj) или Object.entries(obj), а уже его перебирать циклом for...of. Самый ходовой паттерн — for (const [key, value] of Object.entries(obj)).

Почему не стоит использовать for...in для массивов?

Формально работает, но for...in проходит по всем перечисляемым свойствам, включая унаследованные и всё, что кто-то мог дописать в Array.prototype. Плюс ключи возвращаются строками, а порядок обхода числовых индексов гарантирован не во всех случаях. Для значений берите for...of, а если нужен индекс — классический for со счётчиком.

Учитесь программировать с Coddy

НАЧАТЬ