← Назад к вопросам

Откуда появляются доступные значения которые не итерируются

1.8 Middle🔥 191 комментариев
#JavaScript Core

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Отличный вопрос! Он затрагивает фундаментальную концепцию в JavaScript, связанную с итераторами и итерируемыми объектами. Появление "доступных значений, которые не итерируются" — это следствие разделения ответственности между двумя ключевыми протоколами языка: Iterable и Iterator.

Чтобы дать полный ответ, разберем механизм работы итерации с самого начала.

Протоколы: Итерируемый объект (Iterable) vs. Итератор (Iterator)

Это две разные, но тесно связанные сущности:

  • Итерируемый объект (Iterable) — это объект, который реализует метод с ключом Symbol.iterator. Вызов этого метода возвращает итератор. Примеры: массивы (Array), строки (String), объекты Map, Set, arguments, NodeList.
  • Итератор (Iterator) — это объект, который реализует метод next(). Каждый вызов next() возвращает объект с двумя свойствами: value (следующее значение) и done (флаг завершения).

Вот как это выглядит под капотом:

// 1. Создаем итерируемый объект (массив)
const iterableArray = [10, 20, 30];

// 2. Получаем итератор, вызвав Symbol.iterator
const arrayIterator = iterableArray[Symbol.iterator]();

// 3. Вручную вызываем next() у итератора, чтобы получить значения
console.log(arrayIterator.next()); // { value: 10, done: false }
console.log(arrayIterator.next()); // { value: 20, done: false }
console.log(arrayIterator.next()); // { value: 30, done: false }
console.log(arrayIterator.next()); // { value: undefined, done: true }

Ключевой момент: "доступные значения" находятся именно у итератора, в его внутреннем состоянии (позиции), а не у исходного итерируемого объекта. Итерируемый объект — это лишь фабрика по производству итераторов.

Откуда берутся "неитерируемые" доступные значения?

Ситуация, которую вы описываете, возникает, когда мы имеем дело не с "чистым" итерируемым объектом, а с объектом-итератором, который одновременно является и итерируемым. Это распространенный паттерн для упрощения API.

Рассмотрим на примере генератора (Generator) — это самый наглядный случай.

function* numberGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

// Вызов функции-генератора возвращает объект Генератора.
// Этот объект является И ИТЕРАТОРОМ, И ИТЕРИРУЕМЫМ ОБЪЕКТОМ.
const generator = numberGenerator(); // generator - это итератор

// 1. Мы можем использовать его как итератор (вызывать next())
console.log(generator.next()); // { value: 1, done: false }

// 2. Но он также и итерируемый! У него есть Symbol.iterator,
//    который возвращает самого себя.
console.log(generator[Symbol.iterator]() === generator); // true

// 3. Поэтому его можно использовать в for...of.
//    Однако for...of начнет итерацию СНАЧАЛА, получив итератор через Symbol.iterator.
//    Получит он того же самого generator. Но внутреннее состояние generator
//    уже изменено предыдущим вызовом next()!
for (const num of generator) {
  console.log(num); // Выведет: 2, затем 3
}
// Цикл начался со значения 2, потому что значение 1 было уже "потреблено".

Что здесь произошло?

  1. Объект generator при создании хранит в своем внутреннем состоянии доступ к потоку значений [1, 2, 3] и текущую позицию (перед 1).
  2. Первый ручной вызов .next() "потребляет" значение 1 и сдвигает внутреннюю позицию.
  3. Когда for...of пытается итерироваться, он получает тот же самый объект generator в качестве итератора (так как generator[Symbol.iterator]() возвращает generator).
  4. for...of начинает вызывать .next() на этом объекте, получая значения, начиная с текущей позиции (2).

Итог: "Доступные значения, которые не итерируются" в новом цикле — это те значения, которые уже были потреблены из внутреннего состояния общего объекта-итератора до начала новой итерационной конструкции (for...of, spread оператор и т.д.).

Практические примеры и выводы

Это поведение характерно для:

  • Генераторов (Generator) как в примере выше.
  • Потоков данных (ReadableStream), где данные читаются последовательно.
  • Результатов некоторых API (например, курсоры баз данных).
// Другой пример: итератор, полученный из Set
const mySet = new Set([100, 200]);
const setIterator = mySet.values(); // setIterator - это итератор

console.log(setIterator.next().value); // 100
console.log([...setIterator]); // Распространение остатка: [200]
// Значение 100 уже недоступно для spread, оно "потреблено".

Главный вывод: Значения находятся "внутри" объекта-итератора в виде его внутреннего состояния. Если этот объект-итератор мутабелен и его состояние изменяется (вызовами next()), то при его повторном использовании в качестве источника для итерации (через Symbol.iterator) будут доступны только оставшиеся (еще не потребленные) значения. Итерируемый объект (как Array) обычно без состояния — каждый вызов [Symbol.iterator]() создает новый, "свежий" итератор. А объект, совмещающий обе роли (как Generator), передает свое текущее, возможно, "использованное" состояние.

Откуда появляются доступные значения которые не итерируются | PrepBro