Статьи

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

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

Хотя они хорошо играют друг с другом, то, что они на самом деле делают, может немного запутать, поэтому давайте проверим их.

итераторы

Итерация является обычной практикой в ​​программировании и обычно используется для циклического перебора набора значений, либо преобразуя каждое значение, либо используя или сохраняя его каким-либо образом для дальнейшего использования.

В JavaScript у нас всегда были циклы for которые выглядят так:

 for (var i = 0; i < foo.length; i++){ // do something with i } 

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

 for (const i of foo) { // do something with 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[i])) { confirmedPrimes.push(possiblePrimes[i]); } } // confirmedPrimes is now [73, 19] 

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

 const possiblePrimes = [73, 6, 90, 19, 15]; const confirmedPrimes = []; for (const i of possiblePrimes){ if ( isPrime(i) ){ confirmedPrimes.push(i); } } // confirmedPrimes is now [73, 19] 

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

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

Если вам интересны такие подробности, вы можете прочитать больше об этом в этом фантастическом посте в блоге под названием « Итераторы, которые будут повторяться » Джейком Арчибальдом. Это также даст вам хорошее представление о том, что происходит под капотом, когда мы углубимся в другую сторону этой статьи: генераторы.

Генераторы

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

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

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

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

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

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

 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 ключ сообщает вам, done ли функция свою задачу. В нашем случае наша функция никогда не завершится и теоретически может дать нам все простые числа до бесконечности (если бы у нас было столько компьютерной памяти, конечно).

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

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

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

Выводы

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

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

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