问题
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?
回答1:
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:
- Add the
Symbol.iterator
method at the beginning of the answer above to it, or - Make it inherit from %IteratorPrototype%, which already has that method
Sadly, 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.
回答2:
I found that there are some more precise definitions of the terms, and these are the more definitive answers:
According to the ES6 Specs and MDN:
When we have
function* foo() { // note the "*"
yield 1;
yield 3;
yield 5;
}
foo
is called generator function. And then when we have
let bar = foo();
bar
is a generator object. And a generator object conforms to both the iterable protocol and the iterator protocol.
The simpler version is the iterator interface, which is just a .next()
method.
The iterable protocol is: for the object obj
, obj[Symbol.iterator]
gives a "zero arguments function that returns an object, conforming to the iterator protocol".
By the title of the MDN link, it also seems we can also just call a generator object a "generator".
Note that in Nicolas Zakas's book Understanding ECMAScript 6, he probably loosely called a "generator function" as a "generator", and a "generator object" as an "iterator". The take away point is, they are really both "generator" related -- one is a generator function, and one is a generator object, or generator. The generator object conforms to both the iteratable protocol and the iterator protocol.
If it is just an object conforming to the iterator protocol, you cannot use [...iter]
or for (a of iter)
. It has to be an object that conforms to the iterable protocol.
And then, there is also a new Iterator class, in a future JavaScript specs that is still in a draft. It has a larger interface, including methods such as forEach
, map
, reduce
of the current Array interface, and new ones, such as and take
, and drop
. The current iterator refers to the object with just the next
interface.
To answer the original question: what is the difference between an iterator and an iterable, the answer is: an iterator is an object with the interface .next()
, and an iterable is an object obj
such that obj[Symbol.iterator]
can give a zero-argument function that, when invoked, returns an iterator.
And a generator is both an iterable and iterator, to add to that.
来源:https://stackoverflow.com/questions/59458257/in-javascript-es6-what-is-the-difference-between-an-iterable-and-iterator