Как перестать бояться и полюбить Итераторы

Spread the love

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

Статья является свободным переводом статьи Kushan JoshiHow 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).

Была ли вам полезна эта статья?
[5 / 5]

Spread the love
Подписаться
Уведомление о
guest
0 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments