- PVSM.RU - https://www.pvsm.ru -

Symbol.iterator в Javascript

Это короткая, но достаточно полезная статья для продолжающих разработчиков о итераторах в Javascript.

image

Прежде чем узнаем за итераторы в js, вспомним о том, что такое Symbol:

Symbol — это уникальный и иммутабельный идентификатор. Создается с помощью функции Symbol(), также может иметь метку Symbol('foo'). Символы с одинаковыми метками не равны друг другу, и вообще, любые символы не равны между собой (помним про уникальность).

Существуют системные символы, такие как Symbol.iterator , Symbol.toPrimitive и другие. Системные символы используются самим языком, но мы также можем применять их, чтобы изменять дефолтное поведение некоторых объектов.

Символы являются частью спецификации es6, поэтому не поддерживаются в ie, совсем (caniuse [1]).

Про Symbol.iterator

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

const rangeIterator = '0123456789'[Symbol.iterator]();
console.log(rangeIterator.next()); // {value: "0", done: false}
console.log(rangeIterator.next()); // {value: "1", done: false}
console.log(rangeIterator.next()); // {value: "2", done: false}
...
console.log(rangeIterator.next()); // {value: "9", done: false}
console.log(rangeIterator.next()); // {done: true}

Данный пример со строкой работает, так как у String.prototype имеется свой итератор (спека [2]). Список итерируемых типов в js: String, Array, TypedArray, Map, Set. 
Кроме цикла, javascript использует Symbol.iterator в следующих конструкциях: spread operator, yield, destructuring assignment*.

При вызове [Symbol.iterator]() возвращается интерфейс итератора, который выглядит так:

Iterator {
 next(); // возврат следующего значения
}

Метод iterator.next() подготавливает (дальше посмотрим как) и возвращает объект вида:

{
 value - значение, если есть
 done - признак завершенности итераций
}

Применение Symbol.iterator в своих структурах

В качестве примера создадим свою структуру, которую можно проитерировать с помощью for…of и пройтись .next() методом итератора. Посмотрим на применение Symbol.iterator с упомянутыми выше конструкциями языка.

Представим что у нас есть маршрут, проложенный через несколько станций, и мы хотим пройти по маршруту и что-то сделать с каждой станцией, например, вывести в консоли. 

Создадим класс Route:

class Route {
 stations; // список станций на этом маршруте
 iterator; // доступ до итератора

 constructor(stations) {
   this.stations = stations;
 }

 // метод получения станции по id
 get(idx) {
   return this.stations[idx];
 }

 // реализация итератора
 [Symbol.iterator]() {
   this.iterator = new RouteIterator(this);
   return this.iterator; // разберем ниже
 }
}

Как вы можете заметить, наш Route реализует метод Symbol.iterator, таким образом Route является итерируемой сущностью (спека [3]), это означает мы можем пройтись по нему используя for…of (после того как посмотрим реализацию RouteIterator).

Метод [Symbol.iterator]() будет вызван столько раз, сколько обращений к нему было. То есть, если несколько циклов друг за другом пытаются пройтись по route, то на каждый цикл будет вызван [Symbol.iterator](), поэтому для каждого вызова мы создаем новый экземпляр RouteIterator.

Теперь познакомимся с самим RouteIterator. Это класс реализующий интерфейс итератора для Route сущности. Посмотрим на него:

class RouteIterator {
 _route; // доступ до итерируемого объекта
 _nextIdx; // указатель следующего значения

 constructor(route) {
  this._route = route;
  this._nextIdx = 0;
 }

 next() {
  if (this._nextIdx === this._route.stations.length) {
   return { done: true } // проверка на последний элемент
  }

  const result = {
   value: this._route.get(this._nextIdx),
   done: false
  }

  this._nextIdx++;

  return result;
 }
}

В данном классе мы имеем доступ до итерируемой коллекции (свойство route), так же nextIdx - это указатель на следующее значение в нашей коллекции.

Метод next() первым делом проверяет не завершился ли маршрут, и если завершился  -  возвращает что итерации завершены. Иначе мы берем следующее значение в коллекции route, говорим, что итерации не завершены, перемещаем указатель и возвращаем результат. 

Теперь мы можем пройтись по коллекции route через for…of:

for (let item of route) {
 console.log(item);
}

Такой код выведет список станций, который мы передали в Route.

Теперь пройдемся по станциям с помощью .next() метода итератора:

console.log(route.iterator.next()) // {value: Москва, done: false}
console.log(route.iterator.next()) // {value: Питер, done: false}
console.log(route.iterator.next()) // {value: Казань, done: false}
console.log(route.iterator.next()) // {done: true}

Но лучше делать это используя функции генераторы [4]:

function* gen() { yield* route; }
const g = gen();
g.next() // {value: "Москва", done: false}
g.next() // {value: "Питер", done: false}
g.next() // {value: "Казань", done: false}
g.next() // {value: undefined, done: true}

Symbol.iterator используется при деструктуризации:

const [a, b, c] = route;
// a - "Москва"
// b - "Питер"
// с - "Казань"

и со spread оператором:

function test(a, b, c) { console.log(a, b, c) }
test(…route) // "Москва" "Питер" "Казань"

Результаты

Создали свой класс, сделали его итерируемым и использовали с конструкциями javascript'a. Спасибо за внимание =).

Материалы

Невозможно полностью освоить новый материал только одной статьей, поэтому вот несколько дополнительных:
Про паттерн итератор [5] из книги Рефакторинг Гуру
Про Symbol из книги Ильи Кантора [6]и на MDN [7]

Автор: Ильгам Габдуллин

Источник [8]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/340905

Ссылки в тексте:

[1] caniuse: https://caniuse.com/#feat=mdn-javascript_builtins_symbol

[2] спека: https://www.ecma-international.org/ecma-262/10.0/index.html#sec-string.prototype-@@iterator

[3] спека: https://www.ecma-international.org/ecma-262/10.0/index.html#sec-iterable-interface

[4] генераторы: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator

[5] итератор: https://refactoring.guru/ru/design-patterns/iterator

[6] Ильи Кантора : https://learn.javascript.ru/symbol

[7] MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol

[8] Источник: https://habr.com/ru/post/481548/?utm_source=habrahabr&utm_medium=rss&utm_campaign=481548