Iterable и Iterator - это два протокола, представленные в ES6. Эти протоколы могут быть реализованы любым объектом JavaScript с соблюдением некоторых соглашений.

Итерабельный

Протокол Iterable позволяет вам добавлять / настраивать итерационное поведение для объектов JavaScript, чтобы некоторые значения могли быть зациклены с использованием конструкции for..of. В JavaScript некоторые встроенные типы являются встроенными итерациями, например String, Array или Map. В то время как другие типы, такие как Object, не повторяются.

let testStr = "Fun";
for (let ch of testStr) {
  console.log(ch);    // F, u, n respectively as each loop value
}
let testStrIterator = testStr[Symbol.iterator]();
testStrIterator.next();      // {value: "F", done: false}
testStrIterator.next();      // {value: "u", done: false}
testStrIterator.next();      // {value: "n", done: false}
testStrIterator.next();      // {value: undefined, done: true}

Теперь посмотрим, демонстрирует ли Object итеративное поведение.

let colors = {
    red: "#ff0000",
    green: "#00ff00",
    blue: "#0000ff"
}
for (let color of colors) {
    console.log(color);            
}
// TypeError: colors is not iterable

Вы можете сделать объект итерируемым, и для достижения того же самого объект должен реализовать метод @@iterator в любом из объектов в его цепочке прототипов. Метод @@iterator является функцией с нулевым аргументом и возвращает iterator object, соответствующий протоколу Iterator.

Константа JavaScript Symbol.iterator дает вам @@iterator ключ.

Каждый раз, когда итерируемый объект повторяется (с использованием конструкции for..of), вызывается метод @@iterator, и возвращаемый объект-итератор используется для получения значений итерации.

Итератор

Протокол итератора определяет стандартный способ создания последовательности конечных или бесконечных значений и возвращает значение, указывающее, что все значения были сгенерированы.

Протокол Iterator говорит, что объект должен реализовывать метод next(). Объект, реализующий метод next(), вызывается как объект-итератор. Метод next () - это функция с нулевым аргументом, которая всегда должна возвращать объект, имеющий как минимум два свойства, а именно value и done.

value property может иметь любое значение JavaScript, тогда как done property должно иметь значение false для всех итераций и должно иметь значение true, опубликовать последнее значение. Опубликовать последнее значение, value можно не указывать или undefined.

Метод next() должен всегда возвращать объект-итератор (имеющий value и done properties), любое другое возвращаемое значение, не являющееся объектом, выдаст вам ошибку.

Давайте посмотрим, как можно сделать объект итерируемым, например, в следующем фрагменте кода colors object является итерируемым. Значения следует повторять с помощью for..of construct.

let colors = {
    [Symbol.iterator]() {
        let data = ["#ff0000", "#00ff00", "#0000ff"];
        return {
            next: ()=> {
                let itObj = {
                    value: data.length ? data[0]: undefined,
                    done: data.length === 0
                };
                data.shift();
                return itObj;
            }
        };
    }
}
for (let color of colors) {
    console.log(color);     
// #ff0000, #00ff00, #0000ff respectively as each loop value           
}

В приведенном выше фрагменте кода вы видите, что colors object является итеративным объектом, поэтому с помощью ключа @@iterator (доступ к которому осуществляется через константу [Symbol.iterator]) объект итератора может быть возвращен, а его метод next () может использоваться для получения всех значений одно за другим как вы можете увидеть в следующем фрагменте кода.

// gets the iterator object
let colorsIterator = colors[Symbol.iterator]();
colorsIterator.next();  // {value: "#ff0000", done: false}
colorsIterator.next();  // {value: "#00ff00", done: false}
colorsIterator.next();  // {value: "#0000ff", done: false}
colorsIterator.next();  // {value: undefined, done: true}

Создание объекта Iterator с использованием функции ES6 Generator

Другой способ создания объекта итератора, возвращаемого из метода @@iterator, - использование функции ES6 Generator. Функция генератора при вызове всегда возвращает объект-итератор, содержащий метод next(), и не выполняет тело функции генератора. Когда вызывается next() метод итератора, он выполняет тело функции генератора, пока не достигнет первого yield statement. Дальнейшие вызовы метода next() выполняют тело функции генератора от предыдущего yield оператора к следующему, до последнего и возвращает объект, содержащий value и done properties.

let colors = {
    *[Symbol.iterator]() {
        let data = ["#ff0000", "#00ff00", "#0000ff"];
        for (let i = 0; i< data.length; i++) {
            yield data[i];
        }
    }
}
for (let color of colors) {
    console.log(color);     
// #ff0000, #00ff00, #0000ff respectively as each loop value           
}
let colorsIterator = colors[Symbol.iterator]();
colorsIterator.next();  // {value: "#ff0000", done: false}
colorsIterator.next();  // {value: "#00ff00", done: false}
colorsIterator.next();  // {value: "#0000ff", done: false}
colorsIterator.next();  // {value: undefined, done: true}

Генераторы - это функции, которые были введены в ES6, и являются нормальной функцией, за исключением того, что из нее можно выйти и позже повторно войти, сохраняя контекст (привязки переменных), сохраненный при повторных входах.

spread syntax также использует протокол итераций.

console.log([...colors]);
// ["#ff0000", "#00ff00", "#0000ff"]

Использование Iterator с классом ES6

Давайте попробуем разобраться, реализуя итерационное поведение класса ES6. Мы создадим класс Colors , и его constructor будет принимать массив значений цвета. Объекты, созданные в этом классе, должны быть итерируемыми. Посмотрим, как мы можем это реализовать.

class Colors {
    constructor(colors) {
        this.colors = colors || [];
    }
[Symbol.iterator]() {
        return {
            next: () => {
                if (this.currentIndex < this.colors.length) {
                    this.currentIndex++;
                    return {
                        value: this.colors[(this.currentIndex - 1)],
                        done: false
                    }
                } else {
                    this.currentIndex = 0; // resetting index for further loops
                    return {                        
                        done: true
                    };
                }
            },
            currentIndex: 0
        }
    }
}
let themeColors = new Colors(["#ff0000", "#00ff00", "#0000ff"]);
for (let color of themeColors) {
    console.log(color);     // #ff0000, #00ff00, #0000ff respectively as each loop value           
}
let thmIterator = themeColors[Symbol.iterator]();
thmIterator.next();  // {value: "#ff0000", done: false}
thmIterator.next();  // {value: "#00ff00", done: false}
thmIterator.next();  // {value: "#0000ff", done: false}
thmIterator.next();  // {value: undefined, done: true}

Класс Colors class реализует метод @@iterator, используя [Symbol.iterator], который возвращает объект-итератор, содержащий метод next(). Метод next() проходит через массив данных цвета colors, используя currentIndex , и возвращает последовательность значений цвета и значение done property. После завершения итераций currentIndex сбрасывается в ноль, чтобы могла произойти повторная итерация.

Заключение

ES6 представляет новый способ перебора структур данных JavaScript. Чтобы итерации выполнялись гладко, необходимо использовать два протокола: Iterable Protocol и Iterator Protocol. Простые объекты не являются итерируемыми, но вы можете сделать их повторяемыми, и вы можете создавать / настраивать итерационное поведение любых структур данных JavaScript.