Итерировать объект как массив

Первое, что приходит мне на ум, когда я слышу об итераторе, — это массив. А почему бы и нет, массивы очень активно используются в различных языках программирования. Но пока я изучал концепцию итераторов в JS, я понял, что массивы представляют собой лишь часть того, что такое итератор и что он может делать. Для JS-программиста знание того, что такое итератор и как его использовать, может оказаться очень полезным.

Семантика

  1. Итератор — это то, что реализует протокол итератора.
  2. Протокол итератора — это стандартный способ создания последовательности значений и возможного возврата значения после завершения создания последовательности.
  3. Протокол указывает, что итератор должен реализовать метод next. При вызове метода next он возвращает объект со свойствами done и value. Для параметра done устанавливается значение false, если итерация продолжается, и значение true, когда итерация завершена. значение — это значение, созданное итератором. Когда done имеет значение true, значение может быть установлено как неопределенное.
  4. Тип данных является итерируемым, если для него определен метод Symbol.iterator и он удовлетворяет протоколу итератора, как указано в пункте 3.

Примеры

Давайте посмотрим на несколько встроенных итераторов:

  1. Множество
const a = [1, 2, 3];
typeof a; // object
Array.isArray(a); // true
// Check if the array has Symbol.iterator method defined
const arrayIterator = a[Symbol.iterator]();
// Does the iterator have next method available on it
arrayIterator.next(); // {value: 1, done: false}
arrayIterator.next(); // {value: 2, done: false}
arrayIterator.next(); // {value: 3, done: false}
arrayIterator.next(); // {value: undefined, done: true}
// Array is a type of Iterator

2. Список узлов

Другой пример итератора. Откройте консоль на любой веб-странице приличного размера и выполните приведенные ниже команды.

const a = document.querySelectorAll('div');
typeof a; // object
Array.isArray(a); // false. Its a NodeList
// Check if the array has Symbol.iterator method defined
const nodeListIterator = a[Symbol.iterator]();
// Does the iterator have next method available on it
nodeListIterator.next(); // {value: div.butterBar, done: false}
// NodeList is a type of Iterator

Как вы можете видеть выше, объект не является массивом, но все же является итератором.

3. Строка

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

const a = 'hello';
typeof a; // string
// Check if the string has Symbol.iterator method defined
const stringIterator = a[Symbol.iterator]();
// Does the iterator have next method available on it
stringIterator.next(); // {value: 'h', done: false}
// String is a type of Iterator

Можете ли вы создать итератор самостоятельно? Абсолютно!!

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

// Regular object that we want to iterate between from and to
const a = { from: 1, to: 5 };
// Define a function that satisfies the iterator protocol we 
// discussed above
const iteratorProtocol = function () {
    return {
         current: this.from,
         last: this.to,
         next: function() {
            if (this.current > this.last) {
                return { done: true, value: undefined };
            }
            return { done: false, value: this.current++ }
        }
    };
}
// Make the object iterable
a[Symbol.iterator] = iteratorProtocol; 
const iterator = a[Symbol.iterator]();
iterator.next(); // { done: false, value: 1 }
// Voila! We just created an iterator

Прелести итератора

  • Вы можете использовать for…of и получить доступ к значениям итерируемого объекта.
  • Вы можете использовать синтаксис распространения и преобразовать любую итерацию в массив.
  • Заимствование методов из других итераций становится очень простым. Массив в Javascript имеет множество полезных методов, которые можно легко позаимствовать. Отлично!
const a = 'hello';
for (val of a) {
   console.log(val);
}
// 'h', 'e', 'l', 'l', 'o'
const b = [...a]; // ['h', 'e', 'l', 'l', 'o']
Array.prototype.filter.call(a, item => item === 'l'); // ['l', 'l'];
// Wow, I just filtered a string.

Сводка

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

Итераторы — это гораздо больше, чем то, что было рассмотрено выше. Такие темы, как генераторы, асинхронные итераторы, протоколы асинхронных итераторов вообще не были освещены. Им нужна отдельная запись в блоге, но, надеюсь, эта запись в блоге вызвала некоторое любопытство и проложила путь для более глубоких знаний в будущем.