От автора: в этой статье мы разберем, что такое итераторы. Итераторы – это новый способ перебора любой коллекции в цикле в JS. Они были представлены в ES6 и стали крайне популярны из-за того, что они очень полезны и используются в нескольких местах. Мы разберем концепцию итераторов, а также где их использовать. Посмотрим на некоторые примеры того, как реализуются в JavaScript итераторы.
Введение
Представьте следующий массив –
const myFavouriteAuthors = [ 'Neal Stephenson', 'Arthur Clarke', 'Isaac Asimov', 'Robert Heinlein' ];
В какой-то момент вам захочется получить все значения в массиве отдельно и вывести их на экран для дальнейшей манипуляции или выполнения каких-либо действий над ними. Если я спрошу, как бы вы это сделали? Вы ответите – все просто. Я просто переберу их в цикле с помощью for, while, for-of или одного из этих методов перебора. Реализации будут примерно такие –
А теперь представьте, что у вас не предыдущий массив, а пользовательская структура данных, в которой хранятся все ваши авторы. Вот такая –
myFavouriteAuthors – объект, внутри которого находится другой объект allAuthors. allAuthors содержит 3 массива с ключами fiction, scienceFiction и fantasy. Если я попрошу вас перебрать overmyFavouriteAuthors и получить всех авторов, как вы поступите? Вы можете попробовать скомбинировать разные циклы.
Но если вы так сделаете –
for (let author of myFavouriteAuthors) { console.log(author) } // TypeError: {} is not iterable
Вы получите TypeError, которая указывает, что объект не поддается перебору. Давайте узнаем, что такое iterables, и как сделать объект итерируемым. В конце статьи вы узнаете, как использовать цикл for-of на пользовательских объектах, в нашем случае на myFavouriteAuthors.
Iterables и итераторы
В предыдущем разделе вы выявили проблему. Получить всех авторов из пользовательского объекта легко не получится. Нам нужен метод, благодаря которому мы будем получать доступ ко всем внутренним данным последовательно.
Добавим метод getAllAuthors в myFavouriteAuthors, он будет возвращать всех авторов. Вот так –
Это простой подход. С его помощью можно решить нашу текущую задачу и получить всех авторов. Но с такой реализацией может возникнуть пара проблем –
Имя getAllAuthors очень специфично. Если кто-то пишет свой myFavouriteAuthors, он может его назвать retrieveAllAuthors.
Как разработчики, мы всегда должны знать о специфичном методе, возвращающем все данные. В этом случае это getAllAuthors.
getAllAuthors возвращает массив строк всех авторов. А если другой разработчик вернет массив объектов в таком формате –
[ {name: 'Agatha Christie'}, {name: 'J. K. Rowling'}, ... ]
Разработчик должен будет знать точное имя и возвращаемый тип метода, который возвращает все данные.
А что, если мы зададим правило, чтобы имя метода и его возвращаемый тип были фиксированными и неизменными?
Назовем метод iteratorMethod.
Похожий шаг приняли в ECMA, чтобы стандартизировать процесс перебора пользовательских объектов в цикле. Но вместо iteratorMethod в ECMA использовали имя Symbol.iterator. Symbols предлагает уникальные имена, которые не будут конфликтовать с другими именами свойств. У этого итератора будет метод next, который будет возвращать объект с ключами value и done.
Ключ value будет хранить текущее значение. Он может быть любого типа. Ключ done имеет Булев тип. Этот ключ указывает на то, были получены все значения или нет.
Схема поможет понять связь между iterables, итераторами и next.
Из книги Exploring JS автора Axel Rauschmayer –
iterable – это структура данных, которая хочет сделать свои элементы публично доступными. Для этого она реализует метод с ключом Symbol.iterator. Этот метод является фабрикой для итераторов. То есть он будет создавать итераторы.
Итератор – это указатель для перемещения по элементам структуры данных.
Как сделать объект итерируемым
Из предыдущего раздела мы узнали, что необходимо реализовать метод Symbol.iterator. Чтобы задать этот ключ, используем синтаксис вычисляемых свойств. Пример –
Итератор создается в строке 4. Это объект с методом next. Метод next возвращает значение по переменной step. В строке 25 мы получаем iterator. В строке 27 мы вызываем next. Вызываем next, пока done не станет true.
Именно это происходит в цикле for-of. Циклы for-of принимают iterable и создают его итератор. Итератор продолжает вызывать next(), пока done не станет true.
Iterables в JavaScript
В JS много чего можно перебирать в цикле. На первый взгляд это может быть незаметно, но если взглянуть поближе, начнут показываться iterables.
К iterables относятся –
Массивы и TypedArrays
Строки – итерация по каждому символу или кодовой точке Unicode.
Карты – итерация по парам ключ/значение
Наборы – итерация по элементам
arguments — массивоподобная специальная переменная в функциях
DOM элементы (в разработке)
Другие конструкции в JS, использующие iterables
for-of цикл – требует iterable. Иначе выбрасывается TypeError.
for (const value of iterable) { ... }
Деструктуризация массивов – деструктуризация проходит через iterables. Посмотрим, как это происходит. Код –
const array = ['a', 'b', 'c', 'd', 'e']; const [first, ,third, ,last] = array;
Это эквивалент записи –
const array = ['a', 'b', 'c', 'd', 'e']; const iterator = array[Symbol.iterator](); const first = iterator.next().value iterator.next().value // Since it was skipped, so it's not assigned const third = iterator.next().value iterator.next().value // Since it was skipped, so it's not assigned const last = iterator.next().value
Оператор расширения (…)
Код
const array = ['a', 'b', 'c', 'd', 'e']; const newArray = [1, ...array, 2, 3];
Можно записать так
const array = ['a', 'b', 'c', 'd', 'e']; const iterator = array[Symbol.iterator](); const newArray = [1]; for (let nextValue = iterator.next(); nextValue.done !== true; nextValue = iterator.next()) { newArray.push(nextValue.value); } newArray.push(2) newArray.push(3)
Promise.all и Promise.race принимают iterables над Promises.
Карты и наборы
Конструктор Map превращает iterable над парами [key, value] в Map, а конструктор Set превращает iterable над элементами в Set –
const map = new Map([[1, 'one'], [2, 'two']]); map.get(1) // one const set = new Set(['a', 'b', 'c]); set.has('c'); // true
Перед понятием функций генераторов нужно понять итераторы.
Как сделать myFavouriteAuthors итерируемым
Реализация, которая делает myFavouriteAuthors итерируемым.
const myFavouriteAuthors = { allAuthors: { fiction: [ 'Agatha Christie', 'J. K. Rowling', 'Dr. Seuss' ], scienceFiction: [ 'Neal Stephenson', 'Arthur Clarke', 'Isaac Asimov', 'Robert Heinlein' ], fantasy: [ 'J. R. R. Tolkien', 'J. K. Rowling', 'Terry Pratchett' ], }, [Symbol.iterator]() { // Get all the authors in an array const genres = Object.values(this.allAuthors); // Store the current genre and author index let currentAuthorIndex = 0; let currentGenreIndex = 0; return { // Implementation of next() next() { // authors according to current genre index const authors = genres[currentGenreIndex]; // doNotHaveMoreAuthors is true when the authors array is exhausted. // That is, all items are consumed. const doNothaveMoreAuthors = !(currentAuthorIndex < authors.length); if (doNothaveMoreAuthors) { // When that happens, we move the genre index to the next genre currentGenreIndex++; // and reset the author index to 0 again to get new set of authors currentAuthorIndex = 0; } // if all genres are over, then we need tell the iterator that we // can not give more values. const doNotHaveMoreGenres = !(currentGenreIndex < genres.length); if (doNotHaveMoreGenres) { // Hence, we return done as true. return { value: undefined, done: true }; } // if everything is correct, return the author from the // current genre and incerement the currentAuthorindex // so next time, the next author can be returned. return { value: genres[currentGenreIndex][currentAuthorIndex++], done: false } } }; } }; for (const author of myFavouriteAuthors) { console.log(author); } console.log(...myFavouriteAuthors)
Со знаниями, полученными из этой статьи, вы легко поймете принцип работы итераторов. Логика у них немного сложная. Поэтому я прокомментировал код. Но чтобы усвоить и понять концепцию, лучше всего поиграться с кодом в браузере или в узле.
Автор: Brandon Morelli
Источник: https://codeburst.io/
Редакция: Команда webformyself.