Как перестать бояться и полюбить Итераторы
В этой статье подробно описывается не особо известный функционал (среди начинающих программистов), то тем не менее очень нужный который был введен в ES2015: Итераторы.
Статья является свободным переводом статьи Kushan Joshi — How I learned to Stop Looping and Love the Iterator. В оригинальной статье итераторы сравниваются с обычным циклом for и описываются недостатки использования цикла for в сравнение с итераторами. Что на мой взгляд является спорным мнением и поэтому в переводе эта часть пропущена. Зато просто и подробно были описаны итераторы, что мне кажется может быть интересным. При чтение рекомендуется весь код из статьи продублировать в каком-нибудь онлайн редакторе типа https://codesandbox.io/.
Спонсор поста Честный рейтинг хостинг провайдеров по множеству параметров: отзывам клиентов, оценкам базовых параметров хостинга, тарифных предложений, качеству предоставляемых услуг. На данный момент 309 хостеров 1429 отзывов. Пример тематического рейтинга: лучшие хостинги для phpBB .
Знакомство с итерируемыми объектами (iterables)
Iterables можно найти везде в Javascript, вы обязательно использовали их даже, если не знали об этом. Все, что имеет свойство Symbol.iterator, является итеративными объектами то есть iterables.
Давайте посмотрим на простейшую итерируемую строку!
str = 'hello world'; iterator = str[Symbol.iterator](); // StringIterator {} iterator.next(); // gives `h` ... ... iterator.next(); // gives `d` iterator.next(); // gives `undefined` as no more string left.
Тип строки String в javascript поставляется с протоколом итерации, что означает, что теперь мы можем сказать, что строки являются итеративными.
Что такое Symbol и Symbol.iterator?
Эта тема достойна самой статьи, но вкратце Symbol решает проблему создания свойства в объекте, с которыми вы хотите быть уверенными в отсутствие конфликтов с каким-либо существующими свойствами.
По спецификации, в качестве ключей для свойств объекта могут использоваться только строки или символы (Symbol). Ни числа, ни логические значения не подходят, разрешены только эти два типа данных. И Symbol всегда представляет собой уникальный идентификатор, даже если создать множество символов с одинаковым описанием, это всё равно будут разные символы.
Так что больше не нужно использовать
@@@@@\_my_hidden_property\_
в ваших объекта!
Symbol.iterator — это глобально доступная константа, с помощью которой каждый может реализовывать итерационный протокол. Таким образом, вы можете использовать его, чтобы ваш собственный объект реализовал итерацию.
Как мне реализовать протокол итерации для моих пользовательских объектов?
class Rand { [Symbol.iterator] () { let count = 0; return { next: () => ({ value: count++, done: count > 5 }) }; } } var rand = new Rand(); var iterator = rand[Symbol.iterator](); iterator.next();// {value: 0, done: false} iterator.next();// {value: 1, done: false} // .. iterator.next();// {value: 5, done: false} iterator.next();// {value: undefined, done: true}
Давайте подробно рассмотрим этот пример:
- [Symbol.iterator]() Этот странный синтаксис — не что иное, как новый способ ES2015 динамической инициализации свойств.
- Метод Symbol.iterator должен возвращать объект {next} (не забывайте, что все это соглашение/протокол). Мы называем этот объект iterator. (Подробнее об этом в следующем разделе)
- .next() просто увеличивает счетчик count каждый раз, когда он вызывается, и переключается из значения true в значение done, когда count превышает 5.
В чем разница между iterable и iterator?
Повторяй за мной,
- Iterable — это объект, который реализует протокол итерации (то есть у него есть атрибут Symbol.iterator). string, Array, Set, Map — это все итерируемые объекты (то есть iterables)!
class Rand { [Symbol.iterator] () { // Rand имеет метод `Symbol.iterator`, потому это iterable! let count = 0; return { // Возвращает значение `iterator` next: () => ({ value: count++, done: count > 5 }) }; } }
- Iterator это то, что возвращается [Symbol.iterator](), то есть итерируемым объектом iterable.
- Он содержит полезную информацию о состоянии того, где находится текущая итерация и какое значение следует предоставить следующим.
- Любой итератор должен иметь метод .next (помните, что это все соглашение), этот метод используется для получения следующего значения.
- Объект, возвращаемый методом .next(), должен иметь значение {value, done}, где value — текущее значение, а done указывает, завершена ли итерация или нет.
var iterator = rand[Symbol.iterator](); // это iterator iterator.next(); // {value: 0, done: false} iterator.next(); // {value: 1, done: false} ... iterator.next(); // {value: 4, done: false} iterator.next(); // {value: undefined, done: true}
Зачем нужны итераторы?
Вы получаете много суперспособностей бесплатно, если вы включаете итерацию в своем пользовательском объекте или используете любую из встроенных в Javascript итераций, такую как Array, string, Map или Set.
1. Супер сила: Spread
Помните класс Rand, который мы только что определили? Поскольку он является итеративным (iterable), он наследует суперсилу оператора spread.
var rand = new Rand(); var myArray = [...rand]; // [0, 1, 2, 3, 4] // string can also be used since it is an iterable [..."kushan"]; // ["k", "u", "s", "h", "a", "n"]
Примечание: Обратите внимание, мы не делали [… rand[Symbol.iterator]()], так как … ожидается iterable
, а не iterator
.
2. Супер сила: Используй Array.from
Array.from(rand); // [0, 1, 2, 3, 4] Array.from("kushan"); // ["k", "u", "s", "h", "a", "n"]
3. Супер сила: цикл for of
for of — это новый механизм циклов, представленный в ES2015, который понимает только iterables. Он автоматически вызывает Symbol.iterator, который хранит итератор за кулисами и вызывает .next. Он также останавливается, когда итератор возвращает {done: true}.
for(const v of rand) { console.log(v); } /*Output*/ // 0 // 1 // .. // 4
В отличие от оператора spread, цикл for of может принимать как iterable, так и iterator.
var map = new Map([['a', 1], ['b', 2]]); map[Symbol.iterator];// map iterable так как он имеет ключ `Symbol.iterator` // `for of` понимает `iterable` for (const [key, val] of map) { console.log(key, val); // 'a', 1 } // `for of` так понимает iterators var iterator = map[Symbol.iterator](); // возвращает iterator for (const [key, val] of iterator) { console.log(key, val); // 'a', 1 } // .keys() часть `Map` api var keyIterator = map.keys(); // возвращает iterator for (const key of keyIterator) { console.log(key); // 'a' } // .values() часть `Map` api var valueIterator = map.values(); // возвращает iterator for (const val of valueIterator) { console.log(val); // 1' }
4. Супер сила: Destructuring
Это одна из моих любимых демонстраций суперсилы iterables. ES2015 представил destructuring assignment , которое построено на основе итерируемых элементов (iterables). Destruct понимает только iterables!
// array is iterable [a, b] = [10, 20]; // a=10, b=20 // наш класс rand :P [a, b, c] = rand; // a = 0, b = 1, c = 2 // вы даже можете так [a, ...b] = rand; // a = 0, b = [1, 2, 3, 4]
Заключение
Пожалуйста, поделитесь любой другой суперсилой, которую вы знаете при использовании iterables. Я надеюсь, что эта статья поможет вам понять iterables и iterators.
Не забудьте проверить мои предыдущие статьи.
Если вы ❤️эту статью, пожалуйста, поделитесь этой статьей, чтобы ее … распространить (игра слов: spread the words).