Итерируемые объекты в JavaScript
Итерируемые объекты и индексы в JavaScript
Технический фундамент итерации
До выбора конкретного цикла важно понимать механику языка: как значения становятся итерируемыми и по каким правилам движок проходит элементы. Чтобы не ловить ошибки времени выполнения, нужно понимать три вещи:
- что в JS итерируемо;
- где есть индекс, а где его нет;
- какой цикл выбрать под задачу.
Ключевой момент: сначала определяют тип структуры и способ доступа к элементам, и только потом выбирают конкретный цикл.
Простейшая техническая проверка "итерируемо или нет":
const samples = [
[1, 2], // Array
'JS', // String
new Set([1, 2]), // Set
new Map([['a', 1]]), // Map
{ a: 1 }, // plain object
];
for (const value of samples) {
console.log(typeof value[Symbol.iterator] === 'function');
}
// true, true, true, true, false
Индексы есть не у всех коллекций, даже если их можно итерировать:
const arr = ['A', 'B'];
const str = 'JS';
const set = new Set(['x', 'y']);
const map = new Map([['k1', 10], ['k2', 20]]);
console.log(arr[0]); // 'A' (индекс есть)
console.log(str[0]); // 'J' (позиция есть)
console.log(set[0]); // undefined (индекса нет)
console.log(map[0]); // undefined (доступ не по индексу)
Эти два примера дают базовое правило: "итерируемо" не означает "индексируемо".
Краткая памятка по типам и итерации:
Итерируемы из коробки:
ArrayStringSetMap- Typed arrays (
Uint8Array,Int32Arrayи т.д.) - результаты
Object.keys/values/entries(это массивы)
Не итерируемы из коробки:
Object(plain object{})NumberBooleannullundefined
Важно: почти любой объект можно сделать итерируемым вручную, если добавить метод Symbol.iterator.
Iterable и iterator: базовая модель
Iterable - это значение, у которого есть метод Symbol.iterator.
Этот метод возвращает iterator - объект с методом next(), который по шагам отдает данные.
const arr = [10, 20];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 10, done: false }
console.log(iterator.next()); // { value: 20, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
Разные инструменты обхода (for, for...of, spread, Array.from) опираются на этот механизм по-разному.
Проверь себя: почему for...of не может работать там, где нет Symbol.iterator?
Что итерируемо из коробки
Чаще всего ты встретишь:
Array- итерируется по значениям;String- по символам;Set- по значениям;Map- по парам[key, value];- результаты
Object.keys(...),Object.values(...),Object.entries(...)- это массивы, значит они итерируемы.
for (const ch of 'JS') {
console.log(ch); // J, S
}
Где есть индекс, а где его нет
Важно не путать понятия:
- в массиве есть числовой индекс (
arr[0],arr[1]); - в строке есть позиция символа (
str[0]), но строка неизменяема; - в Set нет концепции индекса элемента;
- в Map доступ идет по ключу, не по индексу;
- в обычном объекте
{}доступ идет по ключам свойств.
Проверь себя: почему вопрос "какой индекс у элемента в Set?" некорректен?
Как выбрать цикл: for, for...of, for...in
for используй, когда нужен явный индекс и контроль шага:
const scores = [50, 72, 90];
for (let i = 0; i < scores.length; i++) {
console.log(i, scores[i]);
}
for...of используй, когда нужны значения и индекс не важен:
for (const score of scores) {
console.log(score);
}
for...in проходит по ключам свойств объекта (и может захватывать унаследованные свойства), поэтому для массивов обычно не рекомендуется:
const user = { name: 'Anna', age: 20 };
for (const key in user) {
console.log(key, user[key]);
}
Практичнее для объектов:
for (const [key, value] of Object.entries(user)) {
console.log(key, value);
}
Как получить и значение, и индекс в for...of
Если нужен и индекс, и элемент, используй entries():
const tags = ['js', 'ts', 'node'];
for (const [index, tag] of tags.entries()) {
console.log(index, tag);
}
Это часто читается лучше, чем ручной счетчик, когда шаг всегда +1.
Array-like != iterable
Есть структуры, похожие на массив по виду (length, числовые ключи), но они не всегда итерируемы.
Поэтому "похоже на массив" не равно "можно в for...of".
Безопасный прием для нормализации:
const normalized = Array.from(input);
Смотри, что важно: Array.from(...) умеет работать и с iterable, и с array-like (у которых есть length и элементы по индексам). Но если объект не iterable и не array-like, можно получить «тихий» пустой массив, и это тоже источник багов.
console.log(Array.from({ length: 3, 0: 'a', 1: 'b', 2: 'c' })); // ['a', 'b', 'c']
console.log(Array.from({ a: 1 })); // []
Используй Array.from осознанно: когда ты понимаешь, какой именно формат входа ожидается.
Практика: безопасный перебор входных данных
function printItems(input) {
if (input == null) return; // null/undefined
if (typeof input[Symbol.iterator] !== 'function') {
console.log('Not iterable');
return;
}
for (const item of input) {
console.log(item);
}
}
Такой паттерн защищает от runtime-ошибок, если данные пришли извне.
Дополнительный пример для новичка: как выбрать способ обхода
function describeInput(input) {
if (input == null) return 'no data';
if (typeof input[Symbol.iterator] === 'function') {
let count = 0;
for (const _ of input) count++;
return `iterable(${count})`;
}
if (typeof input === 'object') {
return `object(${Object.keys(input).length})`;
}
return typeof input;
}
Этот пример показывает практический алгоритм: сначала проверка на null, затем проверка итерируемости, и только потом отдельная ветка для обычного объекта.
Частые ошибки новичков
- Пытаться запускать
for...ofпо обычному объекту{}. - Путать
for...in(ключи) иfor...of(значения). - Считать, что индекс есть у любой коллекции.
- Писать
i <= arr.lengthи получать лишнюю итерацию сundefined. - Не проверять входные данные на
null/undefinedперед циклом.
Анти-провал: перед циклом ответь на 3 вопроса: "что за структура?", "нужен ли индекс?", "какой тип результата нужен - ключ, значение или пара?".
Краткий итог
- Итерируемость определяется протоколом
Symbol.iterator. for...ofработает со значениями итерируемых структур.- Индексы характерны в первую очередь для массивов и строк, но не для
Set/Map. - Для объектов чаще выбирают
Object.keys/values/entries+for...of. - Правильный выбор цикла снижает количество ошибок и делает код предсказуемым.