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" и "у"
Больше книг — больше знаний!
Заберите 30% скидку новым пользователям на все книги Литрес с нашим промокодом
ПОЛУЧИТЬ СКИДКУ