Is an iterable the same as an iterator, or are they different?
It seems, from the specifications, an iterable is an object, say, obj
, such that obj[Symbol.iterator]
refers to a function, so that when invoked, returns an object that has a next
method that can return a {value: ___, done: ___}
object:
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)
So in the code above, bar
is the iterable, and wah
is the iterator, and the next()
is the iterator interface.
So, iterable and iterator are different things.
Now, however, in a common example of generator and iterator:
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
In the case above, gen1
is the generator, and iter1
is the iterator, and iter1.next()
will do the proper job. But iter1[Symbol.iterator]
does give a function that, when invoked, gives back iter1
, which is an iterator. So iter1
is both an iterable and iterator in this case?
Besides, iter1
is different from the example 1 above, because the iterable in example 1 can give [1, 3, 5]
as many times as wanted using [...bar]
, while iter1
is an iterable, but since it returns itself, which is the same iterator every time, will only give [1, 3, 5]
once.
So we can say, for an iterable bar
, how many times can [...bar]
give the result [1, 3, 5]
-- and the answer is, it depends. And is iterable the same as an iterator? And the answer is, they are different things, but they can be the same, when the iterable uses itself as the iterator. Is that correct?
An Iterable is basically an object that any user can iterate over. An Iterator is also an object that helps a user in iterating over another object (that is iterable). We can generate an iterator when we pass the object to the iter() method. We use the __next__() method for iterating.
A JavaScript iterable is an object that has a Symbol. iterator. The Symbol. iterator is a function that returns a next() function. An iterable can be iterated over with the code: for (const x of iterable) { }
Iterator is an object which allows us to access a collection of objects one at a time. The following built-in types are by default iterable − String. Array. Map.
The iterable protocol allows JavaScript objects to define or customize their iteration behavior, such as what values are looped over in a for...of construct. Some built-in types are built-in iterables with a default iteration behavior, such as Array or Map , while other types (such as Object ) are not.
Yes, iterables and iterators are different things, but most iterators (including all of the ones you get from JavaScript itself, such as from the keys
or values
methods on Array.prototype
or generators from generator functions) inherit from the %IteratorPrototype% object, which has a Symbol.iterator
method like this:
[Symbol.iterator]() {
return this;
}
The result is that all standard iterators are also iterables. That's so you can use them directly, or use them in for-of
loops and such (which expect iterables, not iterators).
Consider the keys
method of arrays: It returns an array iterator that visits the array's keys (its indexes, as numbers). Note that it returns an iterator. But a common use of it is:
for (const index of someArray.keys()) {
// ...
}
for-of
takes an iterable, not an iterator, so why does that work?
It works because the iterator is also iterable; Symbol.iterator
just returns this
.
Here's an example I use in Chapter 6 of my book: If you wanted to loop over all entries but skip the first one and you didn't want to use slice
to slice off the subset, you can get the iterator, read the first value, then hand off to a for-of
loop:
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);
}
Note that this is all standard iterators. Sometime people show examples of manually-coded iterators like this:
function range(start, end) {
let value = start;
let inc = start < end ? 1 : -1;
return {
next() {
const done = value == end;
const result = {done, value};
if (!done) {
value += inc;
}
return result;
}
};
}
// Works when used directly
const it = range(1, 5);
let result;
while (!(result = it.next()).done) {
console.log(result.value);
}
// Fails when an iterable is expected
try {
for (const value of range(1, 5)) {
console.log(value);
}
} catch (e) {
console.error(e.message);
}
The iterator returned by range
there is not an iterable, so it fails when we try to use it with for-of
.
To make it iterable, we'd need to either:
Symbol.iterator
method at the beginning of the answer above to it, orSadly, TC39 decided not to provide a direct way to get the %IteratorPrototype% object. There's an indirect way (getting an iterator from an array, then taking its prototype, which is defined to be %IteratorPrototype%), but it's a pain.
But there's no need to write iterators manually like that anyway; just use a generator function, since the generator it returns is iterable:
function* range(start, end) {
let value = start;
let inc = start < end ? 1 : -1;
while (value !== end) {
yield value;
value += inc;
}
}
// Works when used directly
const it = range(1, 5);
let result;
while (!(result = it.next()).done) {
console.log(result.value);
}
// Also works when an iterable is expected
for (const value of range(1, 5)) {
console.log(value);
}
In contrast, not all iterables are iterators. Arrays are iterable, but not iterators. So are strings, Maps, and Sets.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With