Полный цикл в digital

Итераторы

Итераторы применяются для организации последовательного доступа к элементам наборов данных - массивам, объектам Set, объектам Map, строкам, благодаря итераторам мы можем перебрать набор данных с помощью цикла for-of:

const people = ["Tom", "Bob", "Sam"];
for(const person of people){
console.log(person);
}

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

Итераторы предоставляют метод next(), который возвращает объект с двумя свойствами: value и done:

{value, done}

Свойство value хранит собственно значение текущего перебираемого элемента, свойство done указывает, есть ли еще в коллекции объекты доступные для перебора. Если в наборе еще есть элементы, то свойство done равно false. Если доступных элементов для перебора больше нет, то это свойство равно true, а метод next() возвращает объект.

{done: true}

Некоторые типы в JavaScript имеют методы которые возвращают итераторы. Например, метод entries() типа Array:

const people = ["Tom", "Bob", "Sam"];
const items = people.entries();
console.log(items.next());

Здесь метод next() возвратит следующий объект на консоль:

{value: Array(2), done: false}
done:	false
value:	Array(2)
0:	0
1:	"Tom"
length:	2
__proto__:	Array(0)
__proto__:	Object

Здесь мы видим, что свойство done имеет значение false, так как мы перебрали только один элемент в множестве, и там еще есть два элемента. Свойство value представляет массив из двух значений. Первое значение представляет ключ или индекс элемента массива, а второй элемент - значение по этому индексу.

Соответственно мы можем организовать и перебор всей коллекции:

const people = ["Tom", "Bob", "Sam"];
// получаем итератор
const items = people.entries();  
while(!(item = items.next()).done){
console.log(item.value);
}

Здесь в цикле while из метода next() итератора получаем текущий объект в переменную item: item = items.next(). Смотрим на ее свойство done - если оно равно false, то есть в наборе еще есть элементы, продолжаем цикл:

while(!(item = items.next()).done){

В цикле обращаемся к свойству value полученного объекта:

console.log(item.value);

Консольный вывод:

[0, "Tom"]
[1, "Bob"]
[2, "Sam"]

Поскольку каждый возвращаемый элемент представляет массив, где первый элемент это индекс в массиве, а второй это само значение, тогда обратившись ко второму элементу, мы можем получить значение:

while(!(item = items.next()).done){
console.log(item.value[1]);
}

В этом нет смысла, поскольку все коллекции которые возвращают итераторы, поддерживают перебор с помощью цикла for...of, который как раз и использует итератор для получения элементов.

Разные объекты могут иметь свою собственную реализацию итератора. И при необходимости мы можем определить объект со своим итератором. Применение итераторов предоставляет нам способ создать объект, который будет вести себя как коллекция элементов.

Для создания перебираемого объекта нам надо определить в объекта метод [Symbol.iterator](). Этот метод собственно и будет представлять итератор:

const iterable = {
[Symbol.iterator]() {
return {
next() {
// если еще есть элементы
return { value: ..., done: false };
// если больше нет элементов
return { value: undefined, done: true };
}
};
}
};

Метод [Symbol.iterator]() возвращает объект, который имеет метод next(). Этот метод возвращает объект с двумя свойствами value и done. Если в нашем объекте есть элементы, то свойство value содержит собственно значение элемента, а свойство done равно false. Если доступных элементов больше нет, то свойство done равно true.

Например, реализуем простейший объект с итератором, который возвращает некоторый набор чисел:

const iterable = {
[Symbol.iterator]() {
return {
current: 1,
end: 3,
next() {
if (this.current <= this.end) {
return { value: this.current++, done: false };
}
return { done: true };
}
};
}
};

Здесь итератор фактически возвращает числе от 1 до 3. Для отслеживания текущего элемента в объекте, который возвращается методом , определены два свойства:

current: 1,
end: 3,

Свойство current собственно хранит значение текущего элемента, свойство end задает предел. То есть в данном случае итератор возвращает числа от 1 до 3.

В методе next(), если текущее значение меньше или равно редельному значению, возвращаем объект:

return { value: this.current++, done: false };

Инкремент this.current++ приведет к тому, что при следующем вызове метода next значение current будет на единицу больше.

Если достигнут предел, то возвращаем объект. Это будет указывать, что объектов больше нет:

return { done: true };

Получим из итератора возвращаемые им элементы:

const myIterator = iterable[Symbol.iterator]();  // получаем итератор
console.log(myIterator.next());  // {value: 1, done: false}
console.log(myIterator.next());  // {value: 2, done: false}
console.log(myIterator.next());  // {value: 3, done: false}
console.log(myIterator.next());  // {done: true}

Здесь сначала получаем итератор в константу myIterator. Затем при обращении к ее методу next() последовательно получаем все элементы. При четвертом вызове метода next условный перебор элементов в итераторе закончен, и метод возвращает объект {done: true}.

Однако если мы хотим перебрать наш объект и получить из него его элементы, то нам не надо обращаться к методу next(). Поскольку объект iterable реализует итератор, то его можно перебрать с помощью цикла for-of:

const iterable = {
[Symbol.iterator]() {
return {
current: 1,
end: 3,
next() {
if (this.current <= this.end) {
return { value: this.current++, done: false };
}
return { done: true };
}
};
}
};
for (const value of iterable) {
console.log(value);
}

Консольный вывод:

1
2
3

Цикл for-of автоматически обращается к методу next() и извлекает значение.

Рассмотрим еще один пример:

const team = {
[Symbol.iterator]() {
return {
index: 0,
members: ["Tom", "Alice", "Kate"],
next() {
if (this.index < this.members.length) {
return { value: this.members[this.index++], done: false };
}
return { done: true };
}
};
}
};
for (const member of team) {
console.log(member);
}

Здесь объект team представляет условную команду и применяет итератор, который возвращает элементы из массива members - участники команды. Для отслеживания индекса текущего возвращаемого элемента из массива members определено свойство index, которое инкрементируется при каждом вызове метода next.

Реализация итератора позволяет рассматривать объект team как условную коллекцию, которую можно перебрать в цикле for-of. Консольный вывод программы:

Tom
Alice
Kate
Заполните форму уже сегодня!
Для начала сотрудничества необходимо заполнить заявку или заказать обратный звонок. В ответ получите коммерческое предложение, которое будет содержать индивидуальную стратегию с учетом требований и поставленных задач
Работаем по будням с 9:00 до 18:00. Заявки, отправленные в выходные, обрабатываем в первый рабочий день до 12:00.
Спасибо, ваш запрос принят и будет обработан!
Эйч Маркетинг