В JavaScript ES6, в чем разница между итерацией и итератором?


14

Является ли итерация такой же, как итератор, или они разные?

Из спецификаций кажется, что итеративный объект - это, скажем, objтакой объект, который obj[Symbol.iterator]ссылается на функцию, поэтому при вызове возвращает объект, у которого есть nextметод, который может вернуть {value: ___, done: ___}объект:

function foo() {
    let i = 0;
    const wah = {
        next: function() {
            if (i <= 2) return { value: (1 + 2 * i++), done: false }
            else return { value: undefined, done: true }
        }
    };
    return wah;     // wah is iterator
}

let bar = {}        // bar is iterable

bar[Symbol.iterator] = foo;

console.log([...bar]);             // [1, 3, 5]   
for (a of bar) console.log(a);     // 1 3 5 (in three lines)

Таким образом, в приведенном выше коде barэто итерация, wahитератор, и next()интерфейс итератора.

Итак, итерируемый и итератор это разные вещи.

Теперь, однако, в общем примере генератора и итератора:

function* gen1() {
    yield 1;
    yield 3;
    yield 5;
}

const iter1 = gen1();

console.log([...iter1]);                           // [1, 3, 5]
for (a of iter1) console.log(a);                   // nothing

const iter2 = gen1();
for (a of iter2) console.log(a);                   // 1 3 5 (in three lines)

console.log(iter1[Symbol.iterator]() === iter1);   // true

В приведенном выше случае gen1это генератор, iter1итератор, и он iter1.next()будет выполнять свою работу правильно. Но iter1[Symbol.iterator]дает функцию, которая при вызове возвращает iter1, которая является итератором. Так iter1и в этом случае итеративный и итератор?

Кроме того, iter1он отличается от приведенного выше примера 1, поскольку итерация в примере 1 может давать [1, 3, 5]столько раз, сколько требуется [...bar]при использовании , в то время как iter1она является итеративной, но поскольку она возвращает себя, то есть каждый раз [1, 3, 5]один и тот же итератор дает только один раз.

Таким образом, мы можем сказать, для повторения bar, сколько раз может [...bar]дать результат [1, 3, 5]- и ответ, это зависит. И повторяется ли итератор? И ответ таков: это разные вещи, но они могут быть одинаковыми, когда итерируемый использует себя в качестве итератора. Это верно?



« Так iter1и в этом случае и итеративный и итератор? » - да. Все нативные итераторы также итерируемы, возвращая себя, так что вы можете легко передать их в конструкции, которые ожидают итерацию.
Берги

Ответы:


10

Да, итерируемыми и итераторы разные вещи, но большинство итераторы ( в том числе все те , которые вы получаете от самого JavaScript, например, из keysили valuesметодов на Array.prototypeили генераторов из функций генератора) наследуются от % IteratorPrototype% объекта , который имеет Symbol.iteratorметод , как это:

[Symbol.iterator]() {
    return this;
}

В результате все стандартные итераторы также являются итеративными. Это значит, что вы можете использовать их напрямую или использовать их в for-ofциклах и т. Д. (Которые ожидают итераторов, а не итераторов).

Рассмотрим keysметод массивов: он возвращает итератор массива, который посещает ключи массива (его индексы в виде чисел). Обратите внимание, что он возвращает итератор . Но общее использование этого:

for (const index of someArray.keys()) {
    // ...
}

for-ofпринимает итеративный , а не итератор , так почему это работает?

Это работает, потому что итератор также итеративен; Symbol.iteratorпросто возвращается this.

Вот пример, который я использую в главе 6 моей книги: если вы хотите перебрать все записи, но пропустить первую и не хотите использовать sliceдля вырезания подмножества, вы можете получить итератор, прочитать первое значение, затем передать в for-ofцикл:

const a = ["one", "two", "three", "four"];
const it = a[Symbol.iterator]();
// Skip the first one
it.next();
// Loop through the rest
for (const value of it) {
    console.log(value);
}

Обратите внимание, что это все стандартные итераторы. Иногда люди показывают примеры кодированных вручную итераторов, например:

Итератор, возвращаемый rangeтам, не является итеративным, поэтому он перестает работать, когда мы пытаемся использовать его с for-of.

Чтобы сделать его итеративным, нам нужно:

  1. Добавьте Symbol.iteratorметод в начале ответа выше или
  2. Сделайте это наследовать от% IteratorPrototype%, который уже имеет этот метод

К сожалению, TC39 решил не предоставлять прямой способ получения объекта% IteratorPrototype%. Есть косвенный путь (получение итератора из массива, затем взятие его прототипа, который определен как% IteratorPrototype%), но это боль.

Но в любом случае нет необходимости писать итераторы подобным образом; просто используйте функцию генератора, поскольку возвращаемый генератор является итеративным:


Напротив, не все итерации являются итераторами. Массивы являются итеративными, но не итераторами. Как и строки, карты и наборы.


0

Я обнаружил, что есть некоторые более точные определения терминов, и это более точные ответы:

Согласно спецификации ES6 и MDN :

Когда у нас

function* foo() {   // note the "*"
    yield 1;
    yield 3;
    yield 5;
}

fooназывается функцией генератора . И тогда, когда у нас есть

let bar = foo();

barявляется генератором объекта . И объект генератора соответствует и итерируемому протоколу и протоколу итератора .

Более простой версией является интерфейс итератора, который является всего лишь .next()методом.

Итерация протокол: для объекта obj, obj[Symbol.iterator]дает «ноль аргументы функцию , которая возвращает объект, в соответствии с протоколом итератора».

Судя по названию ссылки MDN , мы также можем просто назвать объект генератора «генератором».

Обратите внимание, что в книге Николаса Закаса «Понимание ECMAScript 6» он вероятно назвал «функцию генератора» «генератором», а «объект генератора» - «итератором». Вывод заключается в том, что они на самом деле оба связаны с «генератором» - один является функцией генератора, а другой - объектом-генератором или генератором. Объект генератора соответствует как итерируемому протоколу, так и протоколу итератора.

Если это просто объект, соответствующий протоколу итератора , вы не можете использовать [...iter]или for (a of iter). Это должен быть объект, который соответствует итерируемому протоколу.

И еще, есть еще один класс Iterator, в будущих спецификациях JavaScript, который все еще находится в черновике . Она имеет больший интерфейс, включая методы , такие как forEach, map, reduceиз текущего интерфейса массива, а также новых, таких , как и take, и drop. Текущий итератор ссылается на объект только с nextинтерфейсом.

Чтобы ответить на первоначальный вопрос: в чем разница между итератором и итерируемым, ответ таков: итератор - это объект с интерфейсом .next(), а итератор - это objтакой объект , который obj[Symbol.iterator]может дать функцию с нулевым аргументом, которая при вызове возвращает итератор.

И генератор является и итеративным и итератором, чтобы добавить к этому.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.