Итераторы и генераторы ES6

Итераторы и генераторы обычно второстепенны при написании кода, но если вы уделите несколько минут тому, чтобы подумать о том, как их использовать для упрощения кода, они избавят вас от множества проблем отладки и сложности. С новыми итераторами и генераторами ES6 JavaScript получает функциональность, аналогичную Iterable в Java, что позволяет нам настраивать нашу итерацию для объектов. Например, если у вас есть объект Graph, вы можете легко использовать генератор для обхода узлов или ребер. Это делает многое

Итераторы и генераторы обычно второстепенны при написании кода, но если вы уделите несколько минут тому, чтобы подумать о том, как их использовать для упрощения кода, они избавят вас от множества проблем отладки и сложности. С новыми итераторами и генераторами ES6 JavaScript получает функциональность, аналогичную Iterable в Java, что позволяет нам настраивать нашу итерацию для объектов.

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

Итераторы и генераторы ES6

Итераторы

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

Под капотом for...of фактически используется Symbol.iterator . Напомним, символы - это новая функция в JavaScript ES6. Symbol.iterator - это специальный символ, созданный специально для доступа к внутреннему итератору объекта. Итак, вы можете использовать его для получения функции, которая выполняет итерацию по объекту массива, например:

 var nums = [6, 7, 8]; 
 var iterator = nums[Symbol.iterator](); 
 iterator.next();               // Returns { value: 6, done: false } 
 iterator.next();               // Returns { value: 7, done: false } 
 iterator.next();               // Returns { value: 8, done: false } 
 iterator.next();               // Returns { value: undefined, done: true } 

Когда вы используете конструкцию for...of , на самом деле это то, что используется ниже. Обратите внимание на то, как возвращается каждое последующее значение вместе с индикатором, сообщающим вам, находитесь ли вы в подавляющем большинстве случаев, когда вам не нужно использовать next() вручную, как это, но такая возможность есть, если у вас есть вариант использования, требующий более сложного цикла.

Вы можете использовать Symbol.iterator чтобы определить специализированную итерацию для объекта. Допустим, у вас есть собственный объект, являющийся оболочкой для предложений, Sentence .

 function Sentence(str) { 
 this._str = str; 
 } 

Чтобы определить, как мы перебираем внутреннюю часть объекта Sentence, мы предоставляем прототип функции итератора:

 Sentence.prototype[Symbol.iterator] = function() { 
 var re = /\S+/g; 
 var str = this._str; 
 
 return { 
 next: function() { 
 var match = re.exec(str); 
 if (match) { 
 return {value: match[0], done: false}; 
 } 
 return {value: undefined, done: true}; 
 } 
 } 
 }; 

Теперь, используя итератор, который мы только что создали выше (содержащий строку регулярного выражения, которая соответствует только словам), мы можем легко перебирать слова любого предложения, которое мы предоставляем:

 var s = new Sentence('Good day, kind sir.'); 
 
 for (var w of s) { 
 console.log(w); 
 } 
 
 // Prints: 
 // Good 
 // day, 
 // kind 
 // sir. 
Генераторы

Генераторы ES6 построены на основе того, что предоставляют итераторы, за счет использования специального синтаксиса, упрощающего создание итерационной функции. Генераторы определяются с помощью ключевого слова function* Внутри function* вы можете многократно возвращать значения, используя yield . yield используется в функциях генератора для приостановки выполнения и возврата значения. Его можно рассматривать как основанную на генераторе версию ключевого слова return На следующей итерации выполнение возобновится с последней точки, в которой был использован yield

 function* myGenerator() { 
 yield 'foo'; 
 yield 'bar'; 
 yield 'baz'; 
 } 
 
 myGenerator.next(); // Returns {value: 'foo', done: false} 
 myGenerator.next(); // Returns {value: 'bar', done: false} 
 myGenerator.next(); // Returns {value: 'baz', done: false} 
 myGenerator.next(); // Returns {value: undefined, done: true} 

Или вы можете использовать конструкцию for...of :

 for (var n of myGenerator()) { 
 console.log(n); 
 } 
 
 // Prints 
 // foo 
 // bar 
 // baz 

В этом случае мы видим, что генератор затем позаботится о возврате {value: val, done: bool} , который обрабатывается под капотом в for...of .

Итак, как мы можем использовать генераторы в наших интересах? Возвращаясь к нашему предыдущему примеру, мы можем упростить Sentence до следующего кода:

 Sentence.prototype[Symbol.iterator] = function*() { 
 var re = /\S+/g; 
 var str = this._str; 
 var match; 
 while (match = re.exec(str)) { 
 yield match[0]; 
 } 
 }; 

Обратите внимание на то, что функция итератора (теперь Генератора) намного меньше, чем в предыдущей версии. Нам больше не нужно возвращать объект с помощью next функции, и нам больше не нужно иметь дело с возвратом объекта {value: val, done: bool} Хотя в этом примере эта экономия может показаться минимальной, ее полезность будет легко реализована по мере того, как ваши Генераторы будут усложняться.

Преимущества

Как отмечает Джейк Арчибальд , некоторые преимущества этих генераторов:

  • Лень : значения не рассчитываются заранее, поэтому, если вы не выполняете итерацию до конца, вы не потратите зря время на вычисление неиспользуемых значений.

  • Бесконечный : поскольку значения не рассчитываются заранее, вы можете получить бесконечный набор значений. Просто убедитесь, что вы в какой-то момент вырвались из цикла.

  • Итерация строки : благодаря Symbol.iterator String теперь есть собственный итератор, чтобы упростить цикл по символам. Перебор символьных символов строки может быть настоящей болью. Это особенно полезно сейчас, когда JavaScript ES5 поддерживает юникод.

    for (символ строки в строке) {
    console.log (символ);
    }

Заключение

Хотя итераторы и генераторы не являются огромными дополнительными функциями, они действительно помогают очистить код и сохранить его организованность. Сохранение логики итераций с объектом, которому он принадлежит, является хорошей практикой, которая, по-видимому, находится в центре внимания функций ES6. Похоже, что стандарт движется в сторону структурных возможностей и дружественности Java к дизайну, сохраняя при этом скорость разработки динамических языков.

Что вы думаете о новых функциях ES6? Какие у вас есть интересные варианты использования итераторов и генераторов? Дайте нам знать об этом в комментариях!

Спасибо Джейку Арчибальду за прекрасную статью, подробно описывающую, как итераторы и генераторы работают в ES6.

comments powered by Disqus