В современный 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 <= last) { return {
done: false, value: 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 <= this.to) { return {
done: false,
value: 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 (*)
При таком подходе сам объект и хранит состояние итерации (текущий перебираемый элемент).
В данном случае это работает, но для большей гибкости и понятности кода рекомендуется, всё же, выделять итератор в отдельный объект со своим состоянием и кодом.
Do'stlaringiz bilan baham: |