Главная » Статьи » Генераторы и итераторы ES6: Руководство для разработчиков

Генераторы и итераторы ES6: Руководство для разработчиков

Генераторы и итераторы ES6: Руководство для разработчиков

От автора: ES6 предоставил ряд новых функций для языка JavaScript. Две из этих функций, итераторы и генераторы ES6, существенно изменили то, как мы пишем конкретные функции в более сложном интерфейсном коде. Хотя они прекрасно работают друг с другом, то, что они на самом деле делают, может быть немного запутанным, поэтому давайте рассмотрим их подробнее.

Итераторы

Итерация является обычной практикой в программировании и, как правило, используется для обработки через цикл набора значений, преобразуя каждое значение или используя или сохраняя его каким-то образом для последующего использования. В JavaScript у нас всегда был цикл for, который выглядит так:

for (var i = 0; i < foo.length; i++){ // делаем что-то с i
}

Но ES6 дает нам альтернативу:

for (const i of foo) { // делаем что-то с i
}

Это, возможно, более чистая и удобная в работе практика, и напоминает мне такие языки, как Python и Ruby. Но есть кое-что еще, что очень важно отметить: этот новый вид итерации позволяет напрямую взаимодействовать с элементами набора данных.

Представьте, что мы хотим выяснить, является ли каждое число в массиве простым или нет. Мы могли бы сделать это, воспользовавшись функцией, которая делает именно это. Что может выглядеть так:

function isPrime(number) { if (number < 2) { return false; } else if (number === 2) { return true; } for (var i = 2; i < number; i++) { if (number % i === 0) { return false; break; } } return true;
}

Не лучший в мире способ, но он работает. Следующим шагом было бы перебрать наш список чисел и проверить, подходит ли каждое из них для нашей новой блестящей функцией. Это довольно просто:

var possiblePrimes = [73, 6, 90, 19, 15];
var confirmedPrimes = []; for (var i = 0; i < possiblePrimes.length; i++) { if (isPrime(possiblePrimes)) { confirmedPrimes.push(possiblePrimes); }
} // confirmedPrimes теперь - [73, 19]

Опять же, это работает, но это неуклюже, и эта неуклюжесть в значительной степени сводится к тому, как JavaScript обрабатывает циклы. Однако в ES6 в новом итераторе мы получили новый вариант, почти как в Python. Таким образом, предыдущий цикл for можно записать следующим образом:

const possiblePrimes = [73, 6, 90, 19, 15];
const confirmedPrimes = []; for (const i of possiblePrimes){ if ( isPrime(i) ){ confirmedPrimes.push(i); }
} // confirmedPrimes теперь - [73, 19]

Это намного чище, но главное — это цикл for. Переменная i теперь представляет фактический элемент в массиве, с именем possiblePrimes. Таким образом, нам больше не нужно вызывать его по индексу. Это означает, что вместо вызова possiblePrimes в цикле, мы можем просто вызвать i.

Под капотом эта итерация использует блестящий метод ES6 Symbol.iterator(). Этот крутой парень отвечает за описание итерации, а при вызове возвращает объект JavaScript, содержащий следующее значение в цикле, и ключ done, который является либо true, либо false, в зависимости от того, завершен ли цикл.

Генераторы

Генераторы, также называемые «фабрики-итераторы», представляют собой новый тип функции JavaScript, который создает конкретные итерации. Они дают нам особые, самоопределяемые способы перебрать что-то через цикл.

Хорошо, а что все это значит? Давайте рассмотрим пример. Предположим, что нам нужна функция, которая будет давать нам следующее простое число при каждом ее вызове. Опять же, мы будем использовать нашу функцию isPrime, прежде чем проверять, является ли число простым:

function* getNextPrime() { let nextNumber = 2; while (true) { if (isPrime(nextNumber)) { yield nextNumber; } nextNumber++; }
}

Если вы привыкли к «старому» JavaScript, некоторые из этих вещей могут показаться вам похожими на вуду, но на самом деле все не так уж страшно. У нас есть эта странная звездочка после ключевого слова function, но все что она означает — это просто сообщение JavaScript, что мы определяем генератор.

Другим замысловатым элементом является ключевое слово yield. На самом деле это то, что генерирует генератор, когда вы его вызываете. Это примерно эквивалентно возврату, но он сохраняет состояние функции, а не перезапускает все, когда вы его вызываете. Он «запоминает» свое место во время работы, поэтому в следующий раз, когда вы его вызовете, он продолжит с того, на чем остановился.

Это означает, что мы можем сделать следующее:

const nextPrime = getNextPrime();

А затем мы вызываем nextPrime каждый раз, когда мы хотим получить — вы уже догадались — следующий прайм:

console.log(nextPrime.next().value); // 2
console.log(nextPrime.next().value); // 3
console.log(nextPrime.next().value); // 5
console.log(nextPrime.next().value); // 7

Вы также можете просто вызвать nextPrime.next(), что может быть полезно в ситуациях, когда генератор не бесконечен, потому что он возвращает такой объект:

console.log(nextPrime.next());
// {value: 2, done: false}

Здесь ключ done указывает, выполнила ли эта функция свою задачу. В нашем случае функция никогда не закончится и теоретически может дать нам все простые числа до бесконечности (если бы у нас было достаточно много машинной памяти, конечно).

Круто. Так что, могу я использовать генераторы и итераторы прямо сейчас?

Хотя ECMAScript 2015 был завершен и доступен уже несколько лет, поддержка браузерами его функций, в частности генераторов, далека от завершения. Если вы действительно хотите использовать эти и другие современные функции, вы можете ознакомиться с транспилерами, такими как Babel и Traceur, которые преобразуют код ECMAScript 2015 в эквивалентный (по возможности) код ECMAScript 5.

Есть также много онлайн-редакторов с поддержкой ECMAScript 2015 или специально для него предназначенных, особенно Regenerator Facebook и JS Bin. Если вы просто хотите поэкспериментировать и почувствовать, как теперь можно писать JavaScript, вам стоит их попробовать.

Заключение

Генераторы и итераторы дают нам значительную гибкость в подходе к проблемам JavaScript. Итераторы предоставляют способ написания циклов for в стиле Python, что означает, что наш код будет чище и читабельнее.

Генераторы дают нам возможность писать функции, которые помнят, на чем они остановились в последний раз, и могут продолжить с того же места. Они также могут быть бесконечными с точки зрения того, сколько они могут фактически запомнить, что может пригодиться в определенных ситуациях.

Генераторы и итераторы поддерживаются на достаточном уровне. Их поддержка реализована в Node и во всех современных браузерах, за исключением Internet Explorer. Если вам нужно поддерживать старые браузеры, лучше всего использовать транспилер, такой как Babel.

Автор: Byron Houwens

Источник: https://www.sitepoint.com/

Редакция: Команда webformyself.