У 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приймає ітерабельний , а не an ітератор , так чому це працює?

Він працює, тому що ітератор також є ітерабельним; 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.