Iterable и Iterator - это два протокола, представленные в ES6. Эти протоколы могут быть реализованы любым объектом JavaScript с соблюдением некоторых соглашений.
Итерабельный
Протокол Iterable позволяет вам добавлять / настраивать итерационное поведение для объектов JavaScript, чтобы некоторые значения могли быть зациклены с использованием конструкции for..of
. В JavaScript некоторые встроенные типы являются встроенными итерациями, например String
, Array
или Map
. В то время как другие типы, такие как Object
, не повторяются.
let testStr = "Fun"; for (let ch of testStr) { console.log(ch); // F, u, n respectively as each loop value } let testStrIterator = testStr[Symbol.iterator](); testStrIterator.next(); // {value: "F", done: false} testStrIterator.next(); // {value: "u", done: false} testStrIterator.next(); // {value: "n", done: false} testStrIterator.next(); // {value: undefined, done: true}
Теперь посмотрим, демонстрирует ли Object
итеративное поведение.
let colors = { red: "#ff0000", green: "#00ff00", blue: "#0000ff" } for (let color of colors) { console.log(color); } // TypeError: colors is not iterable
Вы можете сделать объект итерируемым, и для достижения того же самого объект должен реализовать метод @@iterator
в любом из объектов в его цепочке прототипов. Метод @@iterator
является функцией с нулевым аргументом и возвращает iterator
object, соответствующий протоколу Iterator.
Константа JavaScript
Symbol.iterator
дает вам@@iterator
ключ.
Каждый раз, когда итерируемый объект повторяется (с использованием конструкции for..of
), вызывается метод @@iterator
, и возвращаемый объект-итератор используется для получения значений итерации.
Итератор
Протокол итератора определяет стандартный способ создания последовательности конечных или бесконечных значений и возвращает значение, указывающее, что все значения были сгенерированы.
Протокол Iterator говорит, что объект должен реализовывать метод next()
. Объект, реализующий метод next()
, вызывается как объект-итератор. Метод next () - это функция с нулевым аргументом, которая всегда должна возвращать объект, имеющий как минимум два свойства, а именно value
и done
.
value
property может иметь любое значение JavaScript, тогда как done
property должно иметь значение false
для всех итераций и должно иметь значение true
, опубликовать последнее значение. Опубликовать последнее значение, value
можно не указывать или undefined
.
Метод
next()
должен всегда возвращать объект-итератор (имеющийvalue
иdone
properties), любое другое возвращаемое значение, не являющееся объектом, выдаст вам ошибку.
Давайте посмотрим, как можно сделать объект итерируемым, например, в следующем фрагменте кода colors
object является итерируемым. Значения следует повторять с помощью for..of
construct.
let colors = { [Symbol.iterator]() { let data = ["#ff0000", "#00ff00", "#0000ff"]; return { next: ()=> { let itObj = { value: data.length ? data[0]: undefined, done: data.length === 0 }; data.shift(); return itObj; } }; } } for (let color of colors) { console.log(color); // #ff0000, #00ff00, #0000ff respectively as each loop value }
В приведенном выше фрагменте кода вы видите, что colors
object является итеративным объектом, поэтому с помощью ключа @@iterator
(доступ к которому осуществляется через константу [Symbol.iterator]
) объект итератора может быть возвращен, а его метод next () может использоваться для получения всех значений одно за другим как вы можете увидеть в следующем фрагменте кода.
// gets the iterator object let colorsIterator = colors[Symbol.iterator](); colorsIterator.next(); // {value: "#ff0000", done: false} colorsIterator.next(); // {value: "#00ff00", done: false} colorsIterator.next(); // {value: "#0000ff", done: false} colorsIterator.next(); // {value: undefined, done: true}
Создание объекта Iterator с использованием функции ES6 Generator
Другой способ создания объекта итератора, возвращаемого из метода @@iterator
, - использование функции ES6 Generator. Функция генератора при вызове всегда возвращает объект-итератор, содержащий метод next()
, и не выполняет тело функции генератора. Когда вызывается next()
метод итератора, он выполняет тело функции генератора, пока не достигнет первого yield
statement. Дальнейшие вызовы метода next()
выполняют тело функции генератора от предыдущего yield
оператора к следующему, до последнего и возвращает объект, содержащий value
и done
properties.
let colors = { *[Symbol.iterator]() { let data = ["#ff0000", "#00ff00", "#0000ff"]; for (let i = 0; i< data.length; i++) { yield data[i]; } } } for (let color of colors) { console.log(color); // #ff0000, #00ff00, #0000ff respectively as each loop value } let colorsIterator = colors[Symbol.iterator](); colorsIterator.next(); // {value: "#ff0000", done: false} colorsIterator.next(); // {value: "#00ff00", done: false} colorsIterator.next(); // {value: "#0000ff", done: false} colorsIterator.next(); // {value: undefined, done: true}
Генераторы - это функции, которые были введены в ES6, и являются нормальной функцией, за исключением того, что из нее можно выйти и позже повторно войти, сохраняя контекст (привязки переменных), сохраненный при повторных входах.
spread
syntax также использует протокол итераций.
console.log([...colors]); // ["#ff0000", "#00ff00", "#0000ff"]
Использование Iterator с классом ES6
Давайте попробуем разобраться, реализуя итерационное поведение класса ES6. Мы создадим класс Colors
, и его constructor
будет принимать массив значений цвета. Объекты, созданные в этом классе, должны быть итерируемыми. Посмотрим, как мы можем это реализовать.
class Colors { constructor(colors) { this.colors = colors || []; } [Symbol.iterator]() { return { next: () => { if (this.currentIndex < this.colors.length) { this.currentIndex++; return { value: this.colors[(this.currentIndex - 1)], done: false } } else { this.currentIndex = 0; // resetting index for further loops return { done: true }; } }, currentIndex: 0 } } } let themeColors = new Colors(["#ff0000", "#00ff00", "#0000ff"]); for (let color of themeColors) { console.log(color); // #ff0000, #00ff00, #0000ff respectively as each loop value } let thmIterator = themeColors[Symbol.iterator](); thmIterator.next(); // {value: "#ff0000", done: false} thmIterator.next(); // {value: "#00ff00", done: false} thmIterator.next(); // {value: "#0000ff", done: false} thmIterator.next(); // {value: undefined, done: true}
Класс Colors
class реализует метод @@iterator
, используя [Symbol.iterator]
, который возвращает объект-итератор, содержащий метод next()
. Метод next()
проходит через массив данных цвета colors
, используя currentIndex
, и возвращает последовательность значений цвета и значение done
property. После завершения итераций currentIndex
сбрасывается в ноль, чтобы могла произойти повторная итерация.
Заключение
ES6 представляет новый способ перебора структур данных JavaScript. Чтобы итерации выполнялись гладко, необходимо использовать два протокола: Iterable Protocol и Iterator Protocol. Простые объекты не являются итерируемыми, но вы можете сделать их повторяемыми, и вы можете создавать / настраивать итерационное поведение любых структур данных JavaScript.