- PVSM.RU - https://www.pvsm.ru -
Это короткая, но достаточно полезная статья для продолжающих разработчиков о итераторах в Javascript.
Прежде чем узнаем за итераторы в js, вспомним о том, что такое Symbol:
Symbol — это уникальный и иммутабельный идентификатор. Создается с помощью функции Symbol(), также может иметь метку Symbol('foo'). Символы с одинаковыми метками не равны друг другу, и вообще, любые символы не равны между собой (помним про уникальность).
Существуют системные символы, такие как Symbol.iterator , Symbol.toPrimitive и другие. Системные символы используются самим языком, но мы также можем применять их, чтобы изменять дефолтное поведение некоторых объектов.
Символы являются частью спецификации es6, поэтому не поддерживаются в ie, совсем (caniuse [1]).
В основном этот символ используется языком в цикле 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 - признак завершенности итераций
}
В качестве примера создадим свою структуру, которую можно проитерировать с помощью 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
Нажмите здесь для печати.