Книга: 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" и "у"

Оглавление книги


Генерация: 0.302. Запросов К БД/Cache: 2 / 0
поделиться
Вверх Вниз