Итераторы введены в ES6 для итерации коллекций в JavaScript.

Задумывались ли вы, как цикл for of или деструктуризация работает за кулисами для перебора отдельных значений. Это из-за итераторов!!!

Итераторы реализованы для следующих типов,

· Множество

· Нить

· Карта

· Набор

В основном мы используем массивы и строки, поэтому мы можем увидеть несколько примеров с этими типами.

Рассмотрим итерацию массива имен ниже через цикл for of

const names = ["John", "David", "Will", "James"];

for (const name of names) {
  console.log(name);
}
// O/P:
// John
// David
// Will
// James

Точно так же, если нам нужно напечатать каждый из символов в строке, мы можем использовать один и тот же цикл for of для его вывода.

for (const char of "John") {
   console.log(char);
}
// O/P:
// J
// O
// H
// N

Итак, что если нам нужно напечатать значения объекта, как показано ниже

const wishList = {
  clothes: ["Shirt - 1 ", "Pant - 1"],
  electronics: ["Mobile", "Laptop"],
  others: ["Speakers", "Chair"],
};

for (const category of wishList) {
   console.log(category);
}

//O/P
Uncaught TypeError: wishList is not iterable
    at <anonymous>:7:24

Это вызовет ошибку «Ошибка: Uncaught TypeError: список желаний не является итерируемым».

Что означает список желаний не является итерируемым?

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

Что такое концепция итераторов, как мы можем реализовать ее в приведенном выше примере?

Есть два способа получить желаемый результат печати элементов списка желаний.

  1. Написание собственного метода для возврата элементов
const wishList = {
  clothes: ["Shirt - 1 ", "Pant - 1"],
  electronics: ["Mobile", "Laptop"],
  others: ["Speakers", "Chair"],
  getWishList() {
    return [...this.clothes, ...this.electronics, ...this.others];
  },
};

for (const category of wishList.getWishList()) {
  console.log(category);
}

// O/P:
// Shirt - 1
// Pant - 1
// Mobile
// Laptop
// Speakers
// Chair

Этот метод имеет некоторые проблемы,

· Имя очень специфично для этого сценария, это может быть getWishLists, retrieveWishList и n число возможностей для имени, и разработчик должен знать это пользовательское имя, чтобы получить данные вместо встроенной функциональности в случае массива или строки

· Тип возвращаемого значения в настоящее время является строкой, как мы знаем об этом объекте, но что, если в будущем какой-нибудь другой разработчик изменит тип возвращаемого значения, например [{имя: «Рубашка — 1» }, {имя: «Пант — 1»}], поэтому тип возвращаемого значения также непредсказуем.

Мы можем достичь решения, учитывая вышеуказанные проблемы, используя итераторы.

const simpleIterableObject = {
  [Symbol.iterator]() {
    let count = 0;
    const iterator = {
      next() {
        if (count === 0) {
          count++;
          return {
            value: "I'm Iterable",
            done: false,
          };
        } else {
          return {
            value: undefined,
            done: true,
          };
        }
      },
    };
    return iterator;
  },
};
for (const iteratorObj of simpleIterableObject) {
  console.log(iteratorObj);
}

//O/P
// I'm Iterable

Структура данных, которая считается итерируемой, если она возвращает «объект итератора», в котором будет метод «next()», и вызов этого метода next() должен возвращать объект со свойствами «value» и «done».

· [Symbol.iterator]() — это уникальный ключ, который не будет переопределять существующие ключи объекта, он вернет объект итератора, который используется циклом for of и будет ли следующий метод внутри объекта итератора

· { value: «I’m Iterable», done: true — это общая структура для всех итерируемых типов.

«значение» — это свойство будет содержать точное значение, которое должно быть доступно в цикле for of.

«done» — этот логический флаг обозначает, завершена ли итерация или нет, если он ложный, он выдаст свойство value внутри цикла, если он ложный, будет считаться, что итерация завершена

Таким образом, вышеуказанные проблемы с пользовательским именем решаются с помощью «[Symbol.iterator()]», а тип возвращаемого значения становится предсказуемым с использованием структуры объекта «{ значение: ‹‹значение››, выполнено:‹‹флаг››}»

Итак, вот полный код для итерации нашего объекта «список желаний».

const wishList = {
  clothes: ["Shirt - 1 ", "Pant - 1"],
  electronics: ["Mobile", "Laptop"],
  others: ["Speakers", "Chair"],
  [Symbol.iterator]() {
    let categoryIndex = 0;
    let productIndex = 0;
    const values = Object.values(this);
    let products;
    const iterator = {
      next() {
        if (!values.length) {
          return {
            value: undefined,
            done: true,
          };
        }
        // Get the next products in the category once all the products in the previous categories are iterated
        if (!products || productIndex === products.length) {
          products = values[categoryIndex];
          productIndex = 0;
          categoryIndex++;
        }
        //Complete the loop if all the categories are iterated
        if (values.length < categoryIndex) {
          return {
            value: undefined,
            done: true,
          };
        }
        //Iterate the products in each
        while (productIndex < products.length) {
          return {
            value: products[productIndex++],
            done: false,
          };
        }
      },
    };

    return iterator;
  },
};
for (const iteratorObj of wishList) {
  console.log(iteratorObj);
}

Таким образом, мы можем имитировать поведение по умолчанию обычных итерируемых типов, таких как Array и String.

Примечание. Мы также можем назначить функцию-генератор свойству [Symbol.iterator]() объекта, так как по умолчанию он возвращает объект-итератор с функцией next(), а вызов метода next() возвращает аналогичная структура, как указано выше.

Удачного кодирования!!!