Книга: JavaScript. Подробное руководство, 6-е издание
11.4.2. Итераторы
11.4.2. Итераторы
В версии JavaScript 1.7 цикл for/in
был дополнен более универсальными возможностями. Цикл for/in
в JavaScript 1.7 стал больше похож на цикл for/in
в языке Python, он позволяет выполнять итерации по любым итерируемым объектам. Прежде чем обсуждать новые возможности, необходимо дать некоторые определения.
Итератором называется объект, который позволяет выполнять итерации по некоторой коллекции значений и хранит информацию о текущей «позиции» в коллекции. Итератор должен иметь метод next().
Каждый вызов метода next()
должен возвращать следующее значение из коллекции. Например, функция counter(),
представленная ниже, возвращает итератор, который, в свою очередь, возвращает последовательность увеличивающихся целых чисел при каждом вызове метода next().
Обратите внимание, что здесь для хранения текущей информации используется область видимости функции, образующая замыкание:
// Функция, возвращающая итератор;
function counter(start) {
let nextValue = Math.round(start); // Частная переменная итератора
return { next: function() {
return nextValue++; }
}; // Вернуть итератор
}
let serialNumberGenerator = counter(1000);
let sn1 = serialNumberGenerator.next(); // 1000
let sn2 = serialNumberGenerator.next(); // 1001
При работе с конечными коллекциями метод next()
итератора возбуждают исключение Stoplteration
, когда в коллекции не остается значений для выполнения очередной итерации. Stoplteration
- это свойство глобального объекта в JavaScript 1.7. Его значением является обычный объект (без собственных свойств), зарезервированный специально для нужд завершения итераций. Обратите внимание, что Stoplteration
не является функцией-конструктором, таким как TypeErгог()
или RangeError().
Ниже приводится пример метода rangelter(),
возвращающего итератор, который выполняет итерации по целым числам в заданном диапазоне:
// Функция, возвращающая итератор диапазона целых чисел
function rangelter(first, last) {
let nextValue = Math.ceil(first);
return {
next: function() {
if (nextValue > last) throw Stoplteration;
return nextValue++;
}
};
}
// Пример неудобной реализации итераций с помощью итератора диапазона,
let r = rangelter(1.5); // Получить объект-итератор
while(true) { // Теперь использовать его в цикле
try {
console.log(г.next()); // Вызвать метод next() итератора
catch(e) {
if (е == Stoplteration) break; // Завершить цикл по Stoplteration else throw e;
}
}
Обратите внимание, насколько неудобно использовать объект-итератор в цикле из-за необходимости явно обрабатывать исключение Stoplteration
. Из-за этого неудобства итераторы редко используются на практике непосредственно. Чаще используются итерируемые объекты. Итерируемый объект представляет коллекцию значений, по которым можно выполнять итерации. Итерируемый объект должен определять метод с именем __iterator__()
(с двумя символами подчеркивания в начале и в конце), возвращающий объект-итератор для коллекции.
В JavaScript 1.7 в цикл for/in
была добавлена возможность работы с итерируемыми объектами. Если значение справа от ключевого слова in является итерируемым объектом, то цикл for/in
автоматически вызовет его метод __iterator__(),
чтобы получить объект-итератор. Затем он будет вызывать метод next()
итератора, присваивать возвращаемое им значение переменной цикла и выполнять тело цикла. Цикл for/in
сам обрабатывает исключение Stoplteration
, и оно никогда не передается программному коду, выполняемому в цикле. Пример ниже определяет функцию range(),
возвращающую итерируемый объект (а не итератор), который представляет диапазон целых чисел. Обратите внимание, насколько проще выглядит цикл for/in
при использовании итерируемого объекта диапазона по сравнению с циклом while
, в котором используется итератор диапазона.
// Вернуть объект, представляющий диапазон.
// Границы диапазона не изменяются
// и хранятся в замыкании.
// Диапазоны могут проверять вхождение, max;
// Возвращает итерируемый объект, представляющий диапазон чисел
function range(min,max) { return {
get min() { return min; },
get max() { return max; },
includes: function(x) {
return min <= x && x <= max;
},
toString: function() { // Диапазоны имеют строковое представление,
return "[" + min + "," + max + "]";
},
__iterator__: function() { // Возможно выполнять итерации по диапазону
let val = Math.ceil(min); // Сохранить текущ. позицию в замыкании.
return { // Вернуть объект-итератор.
next: function() { // Вернуть следующее число в диапазоне.
if (val > max) // Если достигнут конец - прервать итерации
throw StopIteration;
return val++; // Иначе вернуть следующее число
} // и увеличить позицию
};
}
};
}
// Далее демонстрируется, как можно выполнять итерации по диапазону:
for(let і in range(1,10)) console.log(i); // Выведет числа от 1 до 10
Обратите внимание, что, несмотря на необходимость писать метод __iterator__()
и возбуждать исключение Stoplteration
для создания итерируемых объектов и их итераторов, вам не придется (в обычной ситуации) вызывать метод __iterator__()
и/или обрабатывать исключение Stoplteration
- все это сделает цикл for/in
. Если по каким-то причинам потребуется явно получить объект-итератор итерируемого объекта, можно воспользоваться функцией Iterator()
. (Iterator()
- это глобальная функция, которая появилась в версии JavaScript 1.7.) Если передать этой функции итерируемый объект, она просто вернет результат вызова метода __iterator__()
, что придаст дополнительную ясность программному коду. (Если передать функции Iterator()
второй аргумент, она передаст его методу __iterator__()
.)
Однако функция Iterator()
имеет еще одно важное назначение. Если ей передать объект (или массив), не имеющий метода __iterator__()
, она вернет собственную реализацию итерируемого итератора для объекта. Каждый вызов метода next()
этого итератора будет возвращать массив с двумя значениями. В первом элементе массива будет возвращаться имя свойства объекта, а во втором - значение этого свойства. Поскольку этот объект является итерируемым итератором, его можно использовать в цикле for/in
вместо прямого вызова метода next(),
а это означает, что функцию Iterator()
можно использовать совместно с операцией присваивания с разложением при выполнении итераций по свойствам и значениям объекта или массива:
for(let [k,v] in Iterator({a:1,b:2})) // Итерации по ключам и значениям
console.log(k + "=" + v); // Выведет "a=1" и "b=2"
Итератор, возвращаемый функцией Iterator(),
имеет еще две важные особенности. Во-первых, он игнорирует унаследованные свойства и выполняет итерации только по «собственным» свойствам, что чаще всего и требуется. Во-вторых, если передать функции Iterator()
значение true
во втором аргументе, возвращаемый итератор будет выполнять итерации только по именам свойств, без их значений. Обе эти особенности демонстрируются в следующем примере:
о={х:1,у:2} // Объект с двумя свойствами
Object.prototype.z = 3; // Теперь все объекты унаследуют z
for(p in о) console.log(p); // Выведет "x", "у" и ”z"
for(р in Iterator(o, true)) console.log(p); // Выведет только “x" и "у"
- Итераторы
- У15.4 Итераторы фигур
- ГЛАВА 25 Коллекции, перечислители и итераторы
- Эффективное использование STL
- 13.2.9. Реализация параллельных итераторов
- У15.1 Окна как деревья
- У15.2 Является ли окно строкой?
- У15.3 Завершение строительства
- У15.5 Связанные стеки
- У15.6 Кольцевые списки и цепи
- У15.7 Деревья
- У15.9 Плоский precursor (предшественник)