Я узнал об итераторах и генераторах в JavaScript, решая проблему. Итак, мы будем обсуждать эти концепции здесь и пытаться понять концепции, используя ту же проблему, с которой я столкнулся.
Прежде чем рассмотреть проблему и понять, как ее решить, давайте начнем с теории…
Итераторы
Итерируемые объекты — это объекты, которые можно повторять с помощью цикла for...of
.
Все, что является итерируемым, является объектом, в котором будет метод Symbol.iterator
, и когда мы пытаемся повторить его или хотим, чтобы он вел себя как итерируемый (используя цикл for...of
), будет вызван метод Symbol.iterator
, который вернет объект (iterator ). Возвращенный объект будет иметь метод next
, который создаст объект со значением и статусом итерации (используя boolean) — независимо от того, присутствует ли какое-либо другое значение или нет для итерации.
Метод (Symbol.iterator
), возвращающий объект (итератор), в котором есть другой метод (next
) 🤔 — сложный, верно?
Давайте лучше поймем приведенные выше утверждения, решив проблему с использованием итераторов.
Проблема:
Q. Can you create a range(from, to) that makes the following work?
for (let num of range(1, 4)) { console.log(num) } // 1 // 2 // 3 // 4
This is a simple one, could you think of more fancy approaches other than for-loop?
Простейшим решением вышеуказанной проблемы будет следующее:
function range(from, to){ const arr = [];
for(let i = from; i <= to; i++){ arr.push(i); }
return arr; }
Посмотрите на условие задачи еще раз, мы не обязаны возвращать массив, но что-то итерируемое было бы хорошо, так как мы будем использовать цикл
for...of
.
Теперь мы напишем функцию range
, которая будет принимать 2 числа в качестве аргументов и возвращать итерируемый объект.
// to make range() iterable function range(from, to) { return { [Symbol.iterator]: function(){ /* This returned object is known as the "iterator object" in the case of iterators and the "generator object" in the case of generators. */ return { from, to,
next: function() { if(this.from <= this.to){ return {value: this.from++, done: false} }else{ return {done: true} } } } } }; }
for (const item of range(1, 4)) { console.log(item); }
В приведенном выше коде всякий раз, когда мы пытаемся выполнить итерацию с использованием цикла for...of
,
- он вызывает метод
Symbol.iterator
один раз в первый раз и получает объект iterator. - В следующий раз
for...of
использует методnext
для получения следующего значения во время итерации.
Не путайте массивы с итерируемыми объектами. Iterables — это объекты, которые реализуют в нем метод
Symbol.iterator
. Массивы и строки имеют встроенную реализациюSymbol.iterator
вместе со многими другими методами и свойствами, не обязательно, что другие итерации будут иметь то же самое.
Генераторы
Функция-генератор возвращает объект, известный как «объект-генератор», и содержит в себе метод next
.
Пример:
function *generatorExample(){ yield 1; yield 2; }
const generatorObject = generatorExample(); console.log(generatorObject.next()); // {value: 1, done: false}; console.log(generatorObject.next()); // {value: 2, done: false}; console.log(generatorObject.next()); // {value: undefined, done: true};
Когда вызывается метод next
, он выполняет функцию до первого выхода, затем приостанавливает выполнение и возвращает объект с двумя свойствами:
- значение: полученное значение.
- done: логическое значение, указывающее, завершено ли выполнение функции или нет.
Функции генератора можно идентифицировать по
*
в объявлении функции сразу после ключевого словаfunction
(функция*
) или перед именем функции (функция*
имя) без пробела.
Та же самая проблема, упомянутая выше, может быть легко решена с помощью функций генератора следующим образом:
function range(from, to){ return { [Symbol.iterator]: function* (){ for(let i = from; i <= to; i++){ yield i; } } } }
for (const item of range(1, 4)) { console.log(item); } // 1 // 2 // 3 // 4
Функция range
по-прежнему возвращает объект, в котором есть метод Symbol.iterator
. Но разница в том, что Symbol.iterator
теперь выполняет функцию генератора. А как мы знаем, функция-генератор возвращает объект-генератор с методом next
в себе — который нам пришлось реализовать явно для итераторов.
Генераторы также являются итерируемыми.
Тогда мы можем использовать цикл for...of
с генераторами.
function *generatorExample(){ yield 1; yield 2; }
const generatorObject = generatorExample();
for(const item of generatorObject){ console.log(item); } // 1 // 2
Поскольку генератор также является итерируемым, решение можно еще больше упростить, поскольку нам не нужно явно реализовывать метод Symbol.iterator
.
function *range(from, to){ for(let i = from; i <= to; i++){ yield i; } }
for (const item of range(1, 4)) { console.log(item); } // 1 // 2 // 3 // 4
На этом пока все 😀. Спасибо, что дочитали до сих пор🙏.
Если вы хотите узнать о них больше, обратитесь к разделу Итераторы и генераторы — MDN.
Поделитесь этим блогом со своей сетью, если вы нашли его полезным, и не стесняйтесь комментировать, если у вас есть какие-либо сомнения по теме.