Обычно инициализация генераторов выполняется при их создании: аргументы, передаваемые функции-генератору, являются единственными значениями, которые принимают генераторы. Однако имеется возможность передать дополнительные данные уже работающему генератору. Каждый генератор имеет метод
send,
который перезапускает генератор подобно методу
next.
Разница лишь в том, что методу
send
можно передать значение, которое станет значением, возвращаемым
выражением
yield
в функции-генераторе. (В большинстве генераторов, которые не принимают дополнительных входных данных, ключевое слово
yield
выглядит как инструкция. Однако в действительности
yield
– это выражение, возвращающее значение.) Кроме методов
next
и
send
существует еще один способ перезапустить генератор - метод
throw.
Если вызвать этот метод, выражение
yield
возбудит аргумент метода
throw
как исключение, как показано в следующем примере:
// Функция-генератор, ведущая счет от заданного начального значения.
// Метод send позволяет увеличить счетчик на определенное значение.
// Вызов throw("reset") сбрасывает счетчик в начальное значение.
// Это всего лишь пример - здесь метод throw используется не самым лучшим образом,
function counter(initial) {
let nextValue = initial; // Сохранить начальное значение
while(true) {
try {
let increment = yield nextValue; // Вернуть значение и получить приращение
if (increment) // Если передано приращение...
nextValue += increment; // ...использовать его.
else nextValue++; // Иначе увеличить на 1
}
catch (e) { // Если был вызван метод
if (e==="reset") // throw генератора
nextValue = initial; else throw e;
}
}
}
let c = counter(10); // Создать генератор с начальным значением 10
console.log(c.next); //Выведет 10
console.log(c.send(2)); // Выведет 12
console.log(c.throw("reset")); // Выведет 10
11.4.4. Генераторы массивов
Еще одна особенность, заимствованная в JavaScript 1.7 из языка Python, - генераторы массивов. Это механизм инициализации элементов массива на основе элементов другого массива или итерируемого объекта. Синтаксис генераторов массивов основан на математической форме записи элементов множества, т. е. выражения и инструкции находятся совсем не там, где привыкли их видеть программисты на языке JavaScript. Тем не менее привыкание к необычному синтаксису происходит достаточно быстро, а мощь генераторов массивов просто неоценима.
Ниже приводится пример генератора массивов, использующего созданную выше функцию range для инициализации массива, содержащего квадраты четных чисел, меньшие 100:
let evensquares = [х*х for (х in range(0,10)) if (х % 2 === 0)]
Эта строка примерно эквивалентна следующим пяти строкам:
let evensquares = [];
for(x in range(0,10)) {
if (x % 2 === 0)
evensquares.push(x*x);
}
В
общем случае синтаксис генераторов массивов имеет следующий вид:
[ выражение for ( переменная in объект ) if ( условное выражение ) ]
Обратите внимание на три основные части в квадратных скобках:
• Цикл
for/in
или
for/each
без тела. Эта часть генератора массивов включает переменную (или несколько переменных при использовании присваивания с разложением) слева от ключевого слова
in
и объект (который может быть генератором, итерируемым объектом или массивом) справа от ключевого слова
in
. Несмотря на отсутствие тела цикла, эта часть генератора массивов выполняет итерации и присваивает последовательные значения, определяемые переменной. Обратите внимание, что перед именем переменной не допускается указывать ключевое слово
var
или
let
– генераторы массивов неявно используют ключевое слово
let
, а используемая переменная недоступна за пределами квадратных скобок и не затирает существующую переменную с тем же именем.
• После итерируемого объекта может присутствовать ключевое слово
if
и условное выражение. Если оно присутствует, условное выражение используется для фильтрации значений, по которым выполняются итерации. Условное выражение вычисляется после получения каждого значения, воспроизводимого циклом
for
. Если результатом выражения является
false
, это значение пропускается и в массив ничего не добавляется. Ключевое слово
if
можно не указывать - если оно отсутствует, генератор массива действует так, как если бы в нем присутствовала конструкция
if (true)
.
• Выражение, стоящее перед ключевым словом
for
, представляет собой эквивалент тела цикла. После того как значение, возвращаемое итератором, будет присвоено переменной и пройдет проверку условным выражением, будет вычислено значение этого выражения, и полученный результат будет добавлен в создаваемый массив.
Ниже приводятся несколько более конкретных примеров, которые помогут лучше понять синтаксис:
data = [2,3,4, -5]; // Массив чисел
squares = [х*х for each (х in data)]; // Квадраты всех чисел: [4,9,16,25]
// Извлечь квадратные корни из всех неотрицательных элементов
roots = [Math.sqrt(x) for each (x in data) if (x >= 0)]
// Создать массив с именами свойств объекта
о = {а:1, b:2, f: function{}}
let allkeys = [p for (p in o)]
let ownkeys = [p for (p in o) if (o.hasOwnProperty(p))]
let notfuncs = [k for ([k,v] in Iterator(o)) if (typeof v !== "function")]
можно заменить квадратные скобки в генераторах массивов круглыми скобками и получить выражения-генераторы. Выражение-генератор похоже на генератор массивов (синтаксис в круглых скобках в точности соответствует синтаксису в квадратных скобках), но его значением является объект генератора, а не массив. Преимущество выражений-генераторов перед генераторами массивов в том, что они используют прием отложенных вычислений - вычисления выполняются по мере необходимости, а не все сразу - и позволяют обрабатывать даже бесконечные последовательности. Недостаток генераторов состоит в том, что они обеспечивают только последовательный доступ к своим элементам. То есть, в отличие от массивов, генераторы не позволяют обращаться к элементам по индексам: чтобы получить n-е значение, придется выполнить n-1 итераций.
23
На момент написания этих строк выражения-генераторы не поддерживались в Rhino.