Итераторы | Iterators

В ES6 было добавлено такое понятие ка итерируемый объект (Iterable). Iterable - это объект содержимое которого можно перебрать по одному элементу.

Итератор - паттерн проектирования согласно которому источник элементов прячется от клиента. Клиент получает специальный объект с помощью которого он может получить элементы по одному.

Преимущество заключается в том что клиенту не нужно беспокоится о том как итерировать объект. При этом внутри самого объекта автор может использовать любую структуру для хранения элементов и любой алгоритм для их перебеора. Также клиент не получает саму структуру и автору не нужно беспокоиться о том что клиент может случайно или специально её изменить при итерировании.

У итерируемого объекта есть мпециальный метод который возвращает объект. Для доступа к этому методу используется специальный смвол Symbol.iterator.

Iterable {
	[Symbol.iterator]()
}

Объектом который возвращает этот метод является итератор. У итератора есть всего лишь 1 метод: next().

Iterator {
	next()
}

Метод next() - возвращает результат итератора или IteratorResult.

Объект  IteratorResult имеет 2 свойства done и value.

IteratorResult{
	done, 
	value
}

Свойство done - указывает есть ли ещё элементы в последовательности. Свойство value содержит следующий элемент последовательности.

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

Итератор также используется в операторе разворота func(...numbers).

for (let num of numbers){
	console.log(num);
}

Внутри  for...of  использует итератор. Массивы в JS являются итерируемыми объектами. Это значит у них есть скрытый метод  который возвращает итератор. Этот метод спрятан от прмого доступа. Чтбы получить к нему доступ - нужно использовать символ - Symbol.iterator(). Можно написать свой аналог используя

let xmen = ['Cyclops', 'Wolverine', 'Rogue'];

let iterator = xmen[Symbol.iterator](); // сохраняем итератор объекта в переменную

let next = iterator.next(); // вызываем единственный метод итератора 

while (!next.done) {
    console.log(next.value);
    next = iterator.next();
}

ES6 позволяет создавать свои итераторы. Для этого достаточно следовать документации и реализовать все требования к итератору описанные выше. Создадим простой итератор для создания случайных чисел.


let randomGenerator = {
    generate() {
        return this[Symbol.iterator]();
    },
    [Symbol.iterator]() {
        let count = 0;
        return {
            next() {
                let value = Math.ceil(Math.random() * 100);
                let done = count > 9;
                count += 1;
                return { value, done };
            }
        };
    }
};

for (let random of randomGenerator) {
    console.log(random);
}

Вместо цикла for - можно напрямую использовать итератор.

let random = randomGenerator.generate();
console.log(random.next().value);

Такой код работает но несколько неудобно, поэтому лучше создать метод в итераторе который будет отдавать сгенерированное значение. Это похоже на паттерн Factory Method.

let randomGenerator = {
    generate() {
        return this[Symbol.iterator]().next().value;
    },
    [Symbol.iterator]() {
        let count = 0;
        return {
            next() {
                let value = Math.ceil(Math.random() * 100);
                let done = count > 9;
                count += 1;
                return { value, done };
            }
        };
    }
};

let random = randomGenerator.generate();
console.log(randomGenerator.generate());