Итераторы и генераторы обычно второстепенны при написании кода, но если вы уделите несколько минут тому, чтобы подумать о том, как их использовать для упрощения кода, они избавят вас от множества проблем отладки и сложности. С новыми итераторами и генераторами 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.iteratorString теперь есть собственный итератор, чтобы упростить цикл по символам. Перебор символьных символов строки может быть настоящей болью. Это особенно полезно сейчас, когда JavaScript ES5 поддерживает юникод.for (символ строки в строке) {
console.log (символ);
}
Заключение
Хотя итераторы и генераторы не являются огромными дополнительными функциями, они действительно помогают очистить код и сохранить его организованность. Сохранение логики итераций с объектом, которому он принадлежит, является хорошей практикой, которая, по-видимому, находится в центре внимания функций ES6. Похоже, что стандарт движется в сторону структурных возможностей и дружественности Java к дизайну, сохраняя при этом скорость разработки динамических языков.
Что вы думаете о новых функциях ES6? Какие у вас есть интересные варианты использования итераторов и генераторов? Дайте нам знать об этом в комментариях!
Спасибо Джейку Арчибальду за прекрасную статью, подробно описывающую, как итераторы и генераторы работают в ES6.