В ES6 появились итераторы, которые позволяют извлекать данные в необходимом нам порядке. В связи с этим стала доступной возможность задать нужную нам логику перебора конкретного итерируемого объекта.
Таким объектом может быть не только массив, но и большинство сущностей которые можно перебрать, например, списки NodeList в DOM и строки.
Синтаксис:
У итерируемого объекта есть специальный метод, который возвращает объект. Для доступа к этому методу используется специальный символ Symbol.iterator:
let table = {};
table[Symbol.iterator] = function () {
return {
next: function () {
return{
done: true, //или false
value: 1,// Может быть любой значение
}
}
}
}
Как мы видим, у итератора есть метод next(), возвращающий объект, у которого есть два свойства done и value.
Свойство done указывает на наличие значения в последующей итерации, а value содержит следующий элемент последовательности.
Наверное вы подумали о том, что это запись выглядит достаточно странно и почему бы вместо [Symbol.iterator] не использовать просто метод .iterator().
Дело в том, что символ итератора позволяет сделать этот метод уникальным и не доступным для обычного (привычного нам) вызова. Тем самым, если в вашем коде уже есть метод .iterator(), то он не будет конфликтовать и итератором объекта, который заложен в спецификации JavaScript.
Как работает итератор?
Создадим массив с именем arr и объявим новый итератор c именем it:
let arr = [1,2,3];//массив
let it = arr[Symbol.iterator]();// объявление итератора
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
console.log(it.next()); // { value: 3, done: false }
console.log(it.next()); // { value: undefined, done: true }
В данном примере видно, что итератор it не сообщает о завершении своей работы, когда дошел до значения 3 ,т.к done равен false, для этого нам нужно еще раз вызвать метод next() что бы получить значение false. Фактически нам нужно выйти за пределы массива.
Значение false в последнем вызове метода next() дает программе явно понять, что перебор объекта закончен и нужно выходить из цикла.
Для того, что бы понять происходящее предлагаю рассмотреть пример использования итератора в привычном нам цикле for
for(var v, res; (res = it.next()) && !res.done;){
v = res.value;
console.log(v);//1, 2, 3
}
Перед каждой итерацией вызывается метод next() и происходит сравнение с переменной res.done и если она имеет значение true, то выражение получает false и цикл завершается.
В этом примере наглядно показано, что в случае с итераторами, лучше не возвращать в предполагаемом нами последнем значении done: true, т.к. если итератор вернет { done: true, value: 3 }, то итератор не выведет значение 3 и именно на нем завершит цикл. Именно по этому нужно программе «сказать», что нужно сделать последнюю финальную итерацию и уже потом выходить из цикла.
Новый цикл for…of
Что бы облегчить работу и итераторами в ES6 появился новый цикл for..of, который как раз таки работает с итерируемыми объектами и при каждом проходе вызывает метод next() и если в последней итерации получает done: true, то выходит из цикла.
Данный цикл не будет работать с обычными объектами, и если вам надо перебрать свойства объекта, то на это есть for…in.
Пользовательские итераторы
Помимо стандартных итераторов, которые заложены в новой спецификации JavaScript, можно создавать собственные итераторы, например:
const tasks = {
home: [
"Забрать Чампа",
"Зайти в прачечную",
"Исповедаться в грехах"
],
work: [
"Сделать отчет",
"Не проспать",
"Сходить в столовую"
],
study: [
"Сдать алгебру",
"Сходить на химию"
],
[Symbol.iterator](){
const tasks = Object.values(this);
let i = 0;
let taskIndex = 0;
return{
next(){
if(i === tasks[taskIndex].length){
i = 0;
taskIndex++;
}
if(taskIndex === tasks.length){
return{
done: true
}
}
return{
value: tasks[taskIndex][i++],
done: false
}
}
}
}
}
for(let task of tasks){
console.log(task);
}
Этот пример наглядно показывает, как с помощью итератора можно перебрать объект, значениями которого являются массивы. Тем самым из обычного объекта можно сделать итерируемый, который будет работать в цикле for…of и перебираться по нужной нам логике.