Итераторы
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/iterable.
В современный JavaScript добавлена новая концепция «итерируемых» (iterable) объектов.
Итерируемые или, иными словами, «перебираемые» объекты – это те, содержимое которых можно перебрать в цикле.
Например, перебираемым объектом является массив. Но не только он. В браузере существует множество объектов, которые не являются массивами, но содержимое которых можно перебрать (к примеру, список DOM-узлов).
Для перебора таких объектов добавлен новый синтаксис цикла: for..of .
'use strict'; let arr = [1, 2, 3]; // массив — пример итерируемого объекта for (let value of arr) < alert(value); // 1, затем 2, затем 3 >
Также итерируемой является строка:
'use strict'; for (let char of "Привет") < alert(char); // Выведет по одной букве: П, р, и, в, е, т >
Итераторы – расширяющая понятие «массив» концепция, которая пронизывает современный стандарт JavaScript сверху донизу.
Практически везде, где нужен перебор, он осуществляется через итераторы. Это включает в себя не только строки, массивы, но и вызов функции с оператором spread f(. args) , и многое другое.
В отличие от массивов, «перебираемые» объекты могут не иметь «длины» length . Как мы увидим далее, итераторы дают возможность сделать «перебираемыми» любые объекты.
Свой итератор
Допустим, у нас есть некий объект, который надо «умным способом» перебрать.
Например, range – диапазон чисел от from до to , и мы хотим, чтобы for (let num of range) «перебирал» этот объект. При этом под перебором мы подразумеваем перечисление чисел от from до to .
Объект range без итератора:
let range = < from: 1, to: 5 >; // хотим сделать перебор // for (let num of range) .
Для возможности использовать объект в for..of нужно создать в нём свойство с названием Symbol.iterator (системный символ).
При вызове метода Symbol.iterator перебираемый объект должен возвращать другой объект («итератор»), который умеет осуществлять перебор.
По стандарту у такого объекта должен быть метод next() , который при каждом вызове возвращает очередное значение и проверяет, окончен ли перебор.
В коде это выглядит следующим образом:
'use strict'; let range = < from: 1, to: 5 >// сделаем объект range итерируемым range[Symbol.iterator] = function() < let current = this.from; let last = this.to; // метод должен вернуть объект с методом next() return < next() < if (current ; > else < return < done: true >; > > > >; for (let num of range) < alert(num); // 1, затем 2, 3, 4, 5 >
Как видно из кода выше, здесь имеет место разделение сущностей:
- Перебираемый объект range сам не реализует методы для своего перебора.
- Для этого создаётся другой объект, который хранит текущее состояние перебора и возвращает значение. Этот объект называется итератором и возвращается при вызове метода range[Symbol.iterator] .
- У итератора должен быть метод next() , который при каждом вызове возвращает объект со свойствами:
- value – очередное значение,
- done – равно false если есть ещё значения, и true – в конце.
Конструкция for..of в начале своего выполнения автоматически вызывает Symbol.iterator() , получает итератор и далее вызывает метод next() до получения done: true . Такова внутренняя механика. Внешний код при переборе через for..of видит только значения.
Такое отделение функциональности перебора от самого объекта даёт дополнительную гибкость. Например, объект может возвращать разные итераторы в зависимости от своего настроения и времени суток. Однако, бывают ситуации когда оно не нужно.
Если функциональность по перебору (метод next ) предоставляется самим объектом, то можно вернуть this в качестве итератора:
'use strict'; let range = < from: 1, to: 5, [Symbol.iterator]() < return this; >, next() < if (this.current === undefined) < // инициализация состояния итерации this.current = this.from; >if (this.current ; > else < // очистка текущей итерации delete this.current; return < done: true >; > > >; for (let num of range) < alert(num); // 1, затем 2, 3, 4, 5 >// Произойдёт вызов Math.max(1,2,3,4,5); alert( Math.max(. range) ); // 5 (*)
При таком подходе сам объект и хранит состояние итерации (текущий перебираемый элемент).
В данном случае это работает, но для большей гибкости и понятности кода рекомендуется, всё же, выделять итератор в отдельный объект со своим состоянием и кодом.
Оператор spread . и итераторы
В последней строке (*) примера выше можно видеть, что итерируемый объект передаётся через spread для Math.max .
При этом . range интерпретируется как последовательность чисел. То есть произойдёт цикл for..of по range , и его результаты будут использованы в качестве списка аргументов.
Бесконечные итераторы
Возможны и бесконечные итераторы. Например, пример выше при range.to = Infinity будет таковым. Или можно сделать итератор, генерирующий бесконечную последовательность псевдослучайных чисел. Тоже полезно.
Нет никаких ограничений на next , он может возвращать всё новые и новые значения, и это нормально.
Разумеется, цикл for..of по такому итератору тоже будет бесконечным, нужно его прерывать, например, через break .
Встроенные итераторы
Встроенные в JavaScript итераторы можно получить и явным образом, без for..of , прямым вызовом Symbol.iterator .
Например, этот код получает итератор для строки и вызывает его полностью «вручную»:
'use strict'; let str = "Hello"; // Делает то же, что и // for (var letter of str) alert(letter); let iterator = str[Symbol.iterator](); while(true) < let result = iterator.next(); if (result.done) break; alert(result.value); // Выведет все буквы по очереди >
То же самое будет работать и для массивов.
Итого
- Итератор – объект, предназначенный для перебора другого объекта.
- У итератора должен быть метод next() , возвращающий объект , где value – очередное значение, а done: true в конце.
- Метод Symbol.iterator предназначен для получения итератора из объекта. Цикл for..of делает это автоматически, но можно и вызвать его напрямую.
- В современном стандарте есть много мест, где вместо массива используются более абстрактные «итерируемые» (со свойством Symbol.iterator ) объекты, например оператор spread . .
- Встроенные объекты, такие как массивы и строки, являются итерируемыми, в соответствии с описанным выше.
Что такое итератор в javascript
[Коллекции] Итератор в JavaScript: что это такое и как использовать
13 марта 2023
Оценки статьи
Еще никто не оценил статьюИтератор — это объект в JavaScript, который обеспечивает последовательный доступ к элементам коллекции.
В этой статье мы рассмотрим, что такое итераторы, как использовать их на разных типах коллекций, и как итераторы помогают упростить код и сделать его более читабельным.
Что такое итераторы?
Итераторы — это объекты, которые возвращают последовательные значения из коллекции. Они предоставляют метод next() , который возвращает следующий элемент коллекции и свойство done , которое указывает, достигнут ли конец коллекции.
Итераторы используются в цикле for. of , чтобы перебирать элементы коллекции. Цикл for. of автоматически вызывает метод next() для каждого элемента коллекции и останавливается, когда свойство done возвращаемого объекта установлено в true .
Пример использования итератора на массиве
Давайте рассмотрим пример использования итератора на массиве:
const arr = [1, 2, 3]; const iter = arr[Symbol.iterator](); for (const item of iter) console.log(item); >
Здесь мы создаем итератор для массива arr с помощью метода Symbol.iterator() . Затем мы используем цикл for. of , чтобы перебрать элементы массива. Каждый элемент выводится в консоль.
Пример использования итератора на строке
Итераторы могут быть использованы и на строках. Рассмотрим следующий пример:
const str = "hello"; const iter = str[Symbol.iterator](); let result = iter.next(); while (!result.done) console.log(result.value); result = iter.next(); >
Здесь мы создаем итератор для строки str , используя метод Symbol.iterator() . Затем мы используем цикл while , чтобы перебрать каждый символ строки. Каждый символ выводится в консоль.
Пример использования итератора на объекте
Итераторы также могут быть использованы на объектах. Рассмотрим следующий пример:
const obj = a: 1, b: 2, c: 3 >; const iter = Object.values(obj)[Symbol.iterator](); for (const item of iter) console.log(item); >
Здесь мы создаем итератор для значений объекта obj с помощью метода Object.values() . Затем мы используем цикл for. of , чтобы перебрать каждое значение объекта. Каждое значение выводится в консоль.
Использование итераторов для создания собственных коллекций
Мы также можем создать собственные коллекции, которые поддерживают итераторы, чтобы использовать их в цикле for. of .
Для этого нам нужно определить метод Symbol.iterator() в нашей коллекции. Этот метод должен возвращать объект-итератор.
Рассмотрим пример создания собственной коллекции:
const myCollection = items: [1, 2, 3], [Symbol.iterator]() let index = 0; return next: () => if (index this.items.length) return value: this.items[index++], done: false >; > else return done: true >; > > >; > >; for (const item of myCollection) console.log(item); >
Здесь мы создаем объект myCollection , который имеет свойство items , содержащее массив элементов. Мы также определяем метод Symbol.iterator() , который возвращает объект-итератор.
Объект-итератор содержит переменную index , которая указывает на текущий элемент в массиве. Метод next() увеличивает index и возвращает объект со следующим элементом в массиве и свойством done.
Затем мы используем цикл for. of , чтобы перебрать элементы нашей собственной коллекции.
Заключение
Итераторы предоставляют удобный способ получения последовательных значений из коллекций. Они могут быть использованы на разных типах коллекций, включая массивы, строки и объекты.
Мы также можем использовать итераторы для создания собственных коллекций, которые могут быть перебраны с помощью цикла for. of .
Итераторы упрощают код и делают его более читабельным, что делает их полезными инструментами в разработке JavaScript.
Меню категорий
-
Загрузка категорий.
Добро пожаловать в Блог Разработчика Владислава Александровича.
Ведется медленная, но уверенная разработка функционала сайта.
Django Core: 0.3.4 / Next.js 1.0 / UPD: 05.06.2023
Итератор
Разбираемся, как сделать перебор элементов коллекций в порядке, котором хочется.
Время чтения: меньше 5 мин
Открыть/закрыть навигацию по статье
- Кратко
- Как понять
- Зачем это нужно
- Встроенные итераторы
- Где ещё встречается итератор
- Деструктуризация
- Array.from()
- Спред-оператор
- Map, Set
Кратко
Скопировать ссылку «Кратко» Скопировано
Итератор — это объект, который умеет обращаться к элементам коллекции по одному за раз, при этом отслеживая своё текущее положение внутри этой последовательности.
Иными словами итератор — это такой механизм, который позволяет перемещаться (итерироваться) по элементам коллекции в определённом порядке и делает их доступными.
Как понять
Скопировать ссылку «Как понять» Скопировано
В JavaScript итератор — это объект, который возвращает следующий элемент последовательности, через метод next ( ) . Этот метод возвращает объект с двумя свойствами:
- value — значение текущего элемента коллекции.
- done — индикатор, указывающий, есть ли ещё в коллекции значения, доступные для перебора.
function makeIterator(array) let nextIndex = 0 return next: function () if (nextIndex < array.length) const result = nextIndex++ return result > else return > > >>
function makeIterator(array) let nextIndex = 0 return next: function () if (nextIndex array.length) const result = value: array[nextIndex], done: false > nextIndex++ return result > else return done: true > > > > >
После создания, объект-итератор может быть явно использован, с помощью вызовов метода next ( ) :
let iterator = makeIterator(['Hello', 'world'])console.log(iterator.next().value)// 'Hello'console.log(iterator.next().value)// 'world'console.log(iterator.next().done)// true
let iterator = makeIterator(['Hello', 'world']) console.log(iterator.next().value) // 'Hello' console.log(iterator.next().value) // 'world' console.log(iterator.next().done) // true
Как только метод next ( ) завершает перебор, то возвращается < done : true >. Это является сигналом, что итерирование завершено.
Зачем это нужно
Скопировать ссылку «Зачем это нужно» Скопировано
Практически везде, где нужен перебор, он осуществляется через итераторы. Это включает в себя не только строки, массивы, но и другие структуры данных. В современный JavaScript добавлена новая концепция «итерируемых» (iterable) объектов, например Map , представленный в ES6.
Это позволяет перебрать итерируемый объект в цикле for . . of :
for (let value of ['a', 'b', 'c']) console.log(value) // a // b // c>
for (let value of ['a', 'b', 'c']) console.log(value) // a // b // c >
Чтобы быть итерируемым, объект должен реализовать метод @@iterator . Это означает, что он (или один из объектов в цепочке прототипов) должен иметь свойство Symbol . iterator .
Предположим, у нас есть объект person , который представляет набор данных:
const person = name: 'Mark', age: 30, gender: 'male', interests: ['music', 'fishing'],>
const person = name: 'Mark', age: 30, gender: 'male', interests: ['music', 'fishing'], >
Чтобы сделать такой объект итерируемым (и позволить for . . of работать с ним), в нём нужно определить Symbol . iterator :
person[Symbol.iterator] = function () const properties = Object.keys(this) let count = 0 return next() if (count < properties.length) const key = properties[count] let result = count++ return result > else return > >, >>
person[Symbol.iterator] = function () const properties = Object.keys(this) let count = 0 return next() if (count properties.length) const key = properties[count] let result = done: false, value: person[key] > count++ return result > else return done: true > > >, > >
Убедимся, что объект person действительно итерируется:
for (let x of person) console.log(x) // Mark, 30, male, ['music', 'fishing']>
for (let x of person) console.log(x) // Mark, 30, male, ['music', 'fishing'] >
Встроенные итераторы
Скопировать ссылку «Встроенные итераторы» Скопировано
В некоторых случаях интерфейс итератора вызывается по умолчанию. Такие объекты как String , Array , Map и Set являются итерируемыми, потому что их прототипы содержат Symbol . iterator .
Где ещё встречается итератор
Скопировать ссылку «Где ещё встречается итератор» Скопировано
Деструктуризация
Скопировать ссылку «Деструктуризация» Скопировано
При деструктуризации итератор используется для доступа к элементам коллекции:
const [a, b] = new Set(['a', 'b', 'c'])// a// b
const [a, b] = new Set(['a', 'b', 'c']) // a // b
Array . from ( )
Скопировать ссылку «Array.from()» Скопировано
Array . from ( ) позволяет конвертировать итерируемый объект в массив:
const arr = Array.from(new Set(['a', 'b', 'c']))// ['a', 'b', 'c']
const arr = Array.from(new Set(['a', 'b', 'c'])) // ['a', 'b', 'c']
Спред-оператор
Скопировать ссылку «Спред-оператор» Скопировано
Спред-оператор (spread) также вызывает интерфейс итератора по умолчанию:
const arr = [. new Set(['a', 'b', 'c'])]// ['a', 'b', 'c']
const arr = [. new Set(['a', 'b', 'c'])] // ['a', 'b', 'c']
Map , Set
Скопировать ссылку «Map, Set» Скопировано
Конструкторы Map и Set преобразуют итерируемые значения в соответственно Map и Set :
const map = new Map([ ['uno', 'one'], ['dos', 'two'],])map.get('uno')// onemap.get('dos')// two const set = new Set(['red', 'green', 'blue'])set.has('red')// trueset.has('yellow')// false
const map = new Map([ ['uno', 'one'], ['dos', 'two'], ]) map.get('uno') // one map.get('dos') // two const set = new Set(['red', 'green', 'blue']) set.has('red') // true set.has('yellow') // false
Коллекции и итераторы
Итераторы применяются для организации последовательного доступа к элементам наборов данных — массивам, объектам Set, Map, строкам и т.д..Так, благодаря итераторам мы можем перебрать набор данных с помощью цикла for-of :
const people = [«Tom», «Bob», «Sam»]; for(const person of people)
В цикле for-of справа от оператора of указывается набор данных или перебираемый объект, из которого в цикле мы можем получить отдельные элементы. Но эта возможность перебора некоторого объекта, как, например, массива в примере выше, реализуются благодаря тому, что эти объекты применяют итераторы. Рассмотрим подробнее, что представляют итераторы и как можно создать свой итератор.
Итераторы предоставляют метод next() , который возвращает объект с двумя свойствами: value и done
Свойство value хранит собственно значение текущего перебираемого элемента. А свойство done указывает, есть ли еще в коллекции объекты, доступные для перебора. Если в наборе еще есть элементы, то свойство done равно false Если же доступных элементов для перебора больше нет, то это свойство равно true , а метод next() возвращает объект
Некоторые типы в JavaScript имеют методы, которые возвращают итераторы. Например, метод entries() типа Array (то есть массивов):
const people = ["Tom", "Bob", "Sam"]; const items = people.entries(); console.log(items.next());
Здесь метод next() возвратит следующий объект на консоль:
done: false value: Array(2) 0: 0 1: "Tom" length: 2 __proto__: Array(0) __proto__: Object
Здесь мы видим, что свойство done имеет значение false , так как мы перебрали только один элемент в множестве, и там еще есть два элемента.
Свойство value представляет массив из двух значений. Первое значение представляет ключ или индекс элемента массива, а второй элемент — значение по этому индексу. имеет ключ и значение.
Соответственно мы можем организовать и перебор всей коллекции:
const people = [«Tom», «Bob», «Sam»]; const items = people.entries(); // получаем итератор while(!(item = items.next()).done)
Здесь в цикле while из метода next() итератора получаем текущий объект в переменную item : item = items.next()
И смотрим на ее свойство done — если оно равно false (то есть в наборе еще есть элементы), то продолжаем цикл
while(!(item = items.next()).done)
В цикле обращаемся к свойству value полученного объекта
console.log(item.value);
[0, "Tom"] [1, "Bob"] [2, "Sam"]
Поскольку каждый возвращаемый элемент представляет массив, где первый элемент - индекс в массиве, а второй - само значение, то, обратившись ко второму элементу, мы можем получить, значение:
while(!(item = items.next()).done)
Но в этом нет смысла, поскольку все коллекции, которые возвращают итераторы, поддерживают перебор с помощью цикла for. of , который как раз и использует итератор для получения элементов.
Разные объекты могут иметь свою собственную реализацию итератора. И при необходимости мы можем определить объект со своим итератором. Применение итераторов предоставляет нам способ создать объект, который будет вести себя как коллекция элементов
Для создания перебираемого объекта нам надо определить в объекта метод [Symbol.iterator]() . Этот метод собственно и будет представлять итератор:
const iterable = < [Symbol.iterator]() < return < next() < // если еще есть элементы return < value: . done: false >; // если больше нет элементов return < value: undefined, done: true >; > >; > >;
Метод [Symbol.iterator]() возвращает объект, который имеет метод next() . Этот метод возвращает объект с двумя свойствами value и done .
Если в нашем объекте есть элементы, то свойство value содержит собственно значение элемента, а свойство done равно false .
Если доступных элементов больше нет, то свойство done равно true .
Например, реализуем простейший объект с итератором, который возвращает некоторый набор чисел:
const iterable = < [Symbol.iterator]() < return < current: 1, end: 3, next() < if (this.current ; > return < done: true >; > >; > >;
Здесь итератор фактически возвращает числе от 1 до 3. Для отслеживания текущего элемента в объекте, который возвращается методом , определены два свойства:
current: 1, end: 3,
Свойство current собственно хранит значение текущего элемента. А свойство end задает предел. То есть в данном случае итератор возвращает числа от 1 до 3.
В методе next() , если текущее значение меньше или равно редельному значению, возвращаем объект
return < value: this.current++, done: false >;
Инкремент this.current++ приведет к тому, что при следующем вызове метода next значение current будет на единицу больше.
Если достигнут предел, то возвращаем объект
return < done: true >;
Это будет указывать, что объектов больше нет.
Получим из итератора возвращаемые им элементы:
const myIterator = iterable[Symbol.iterator](); // получаем итератор console.log(myIterator.next()); // console.log(myIterator.next()); // console.log(myIterator.next()); // console.log(myIterator.next()); //
Здесь сначала получаем итератор в константу myIterator . Затем при обращении к ее методу next() последовательно получаем все элементы. При четвертом вызове метода next условный перебор элементов в итераторе закончен, и метод возвращает объект .
Однако если мы хотим перебрать наш объект и получить из него его элементы, то нам не надо обращаться к методу next() . Поскольку объект iterable реализует итератор, то его можно перебрать с помощью цикла for-of :
const iterable = < [Symbol.iterator]() < return < current: 1, end: 3, next() < if (this.current ; > return < done: true >; > >; > >; for (const value of iterable)
1 2 3
Цикл for-of автоматически обращается к методу next() и извлекает значение.
Рассмотрим еще один пример:
const team = < [Symbol.iterator]() < return < index: 0, members: ["Tom", "Alice", "Kate"], next() < if (this.index < this.members.length) < return < value: this.members[this.index++], done: false >; > return < done: true >; > >; > >; for (const member of team)
Здесь объект team представляет условную команду и применяет итератор, который возвращает элементы из массива members - участники команды. Для отслеживания индекса текущего возвращаемого элемента из массива members определено свойство index , которое инкрементируется при каждом вызове метода next.
Реализация итератора позволяет рассматривать объект team как условную коллекцию, которую можно перебрать в цикле for-of . Консольный вывод программы: