Iteration refers to the process of continuously extracting data from a data set in a certain order.
So what is the difference between iteration and traversal?
In JavaScript, an iterator is an object that can call next
method to implement iteration. This method returns a An object with two properties.
value
: The next value of the iterable objectdone
: Indicates whether all the data has been retrieved. false
means there is still data, true
means there is no data later.to generate iterators through the iterator factory function Symbol.iterator
in the iterable object.
const arr = []console.log(arr)
const arr = [1, 2, 3] const iter1 = arr[Symbol.iterator]() // Generate an iterator through the iterator factory function `Symbol.iterator`. console.log(iter1) console.log(iter1.next()) console.log(iter1.next()) console.log(iter1.next()) console.log(iter1.next()) console.log('%c%s', 'color:red;font-size:24px;', '================') const mymap = new Map() mymap.set('name', 'clz') mymap.set('age', 21) const iter2 = mymap[Symbol.iterator]() // Generate an iterator through the iterator factory function `Symbol.iterator`. console.log(iter2) console.log(iter2.next()) console.log(iter2.next()) console.log(iter2.next())
It can be found that the iterator is completed after taking the last value, that is, when the next value
of the iterator is undefined
.
However, the above statement is not very accurate. It is not completed when the next value
of the iterator is undefined
. You also need to determine whether there is really no value, or whether there is a value in the iterable object that undefined
. If there is a value in the iterable object that is undefined
, it will not become completed at this time.
const arr = [1, 2, 3, undefined] const iter1 = arr[Symbol.iterator]() // Generate an iterator through the iterator factory function `Symbol.iterator`. console.log(iter1) console.log(iter1.next()) console.log(iter1.next()) console.log(iter1.next()) console.log(iter1.next()) console.log(iter1.next())
can call the iterator factory function multiple times without interfering with each other to generate multiple iterators. Each iterator represents a one-time ordered traversal of the iterable object. Different iterators do not interfere with each other and will only traverse iterable objects independently .
const arr = [1, 2, 3] const iter1 = arr[Symbol.iterator]() // Generate an iterator through the iterator factory function `Symbol.iterator`. const iter2 = arr[Symbol.iterator]() console.log('Iterator1:', iter1.next()) console.log('Iterator2:', iter2.next()) console.log('Iterator1:', iter1.next()) console.log('Iterator2:', iter2.next())
const arr = [1, 2, 3] const iter = arr[Symbol.iterator]() for (const i of iter) { console.log(i) // Output 1, 2, 3 in sequence }
If the iterable object is modified during the iteration, the result obtained by the iterator will also be modified.
const arr = [1, 2, 3] console.log(arr) const iter = arr[Symbol.iterator]() console.log(iter.next()) arr[1] = 999 console.log(iter.next()) console.log(iter.next())
When we iterate to done: true
, will an error be reported when calling next
, or nothing will be returned?
However, no, the iterator will be in a completed but not completed state. done: true
means it has been completed, but next
can still be called in the future, although the result will always be { value: undefined, done: true }
. That's why it's said to be done but not done .
const arr = [1, 2, 3] const iter = arr[Symbol.iterator]() console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) console.log(iter.next())
From the above example, we can know that the iterator is generated through the iterator factory function Symbol.iterator
, so we need to implement an iterator iterator factory function, and then the iterator can call the next
method, so You also need to implement a next
method. As for the iterator factory function, it actually returns the instance this
directly.
Counter example:
class Counter { constructor(limit) { this.count = 1 this.limit = limit } next() { if (this.count <= this.limit) { return { done: false, value: this.count++ } } else { return { done: true, value: undefined } } } [Symbol.iterator]() { return this }}
const counter = new Counter(3) const iter = counter[Symbol.iterator]() console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) console.log(iter.next())
At first glance, there is no problem, but if we use for-of
to traverse, we can find the problem.
const counter = new Counter(3)for (let i of counter) { console.log(i)}console.log('Another iteration:')for (let i of counter) { console.log(i)}
Using for-of
loop also makes it disposable. This is because count
is a variable of this instance, so the same variable is used in both iterations. However, after the first loop of the variable, it has exceeded the limit, so you will not get anything by using for-of
loop again. The result was.
You can put the count
variable in a closure, and then return the iterator through the closure, so that each iterator created will correspond to a new counter.
class Counter { constructor(limit) { this.limit = limit } [Symbol.iterator]() { let count = 1 const limit = this.limit return { // The iterator factory function must return an object with a next method, because iteration is actually implemented by calling the next method next() { if (count <= limit) { return { done: false, value: count++ } } else { return { done: true, value: undefined } } } } }}
Test
const counter = new Counter(3)for (let i of counter) { console.log(i)}console.log('Another iteration:')for (let i of counter) { console.log(i)}
is just like using for-of
loop. The iterator will smartly call next
method. When the iterator terminates early, it will also call the return
method.
[Symbol.iterator]() { let count = 1 const limit = this.limit return { // The iterator factory function must return an object with a next method, because iteration is actually implemented by calling the next method next() { if (count <= limit) { return { done: false, value: count++ } } else { return { done: true, value: undefined } } }, return() { console.log('Termination of iterator early') return { done: true } } }}
Test
const counter = new Counter(5)for (let i of counter) { if (i === 3) { break; } console.log(i)}
If the iterator is not closed, you can continue iterating from where you left off . Array iterators cannot be closed.
const arr = [1, 2, 3, 4, 5]const iter = arr[Symbol.iterator]()iter.return = function () { console.log('Exit the iterator early') return { done: true }}for (const i of iter) { console.log(i) if (i === 2) { break }}for (const i of iter) { console.log(i)}