The iterator
Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
An iterator is an object that helps us traverse a data structure or a container, such as a linked list or array
When iterators are used to traverse an object, we do not need to care about the internal implementation details of the object being traversed
- Behaving like a cursor in a database, iterators first appeared in the CLU programming language, which was designed in 1974
- Iterators are implemented differently in various programming languages, but there are almost all iterators, such as Java, Python, etc
An iterator is also a concrete object in JS. This object must conform to the Iterator Protocol.
That is, in JS, a valid iterator is an object that implements the correct next method
The next method has the following requirements:
-
A function with no arguments or one argument returns an object that should have the following two properties:
- done(boolean)
- False if the iterator can produce the next value in the sequence (without traversing)
- True if the iterator has iterated through the sequence.
- The value (any)
-
Any JavaScript value returned by the iterator
-
Done can be omitted when true, but is not recommended [if it still exists, it is the default return value at the end of the iteration (i.e. undefined)]
-
- done(boolean)
The sample
const users = ['Klaus'.'Alex'.'Steven']
function createIterator(arr) {
let index = 0
return {
next() {
return {
done: index >= arr.length,
value: arr[index++]
}
}
}
}
const iterator = createIterator(users)
console.log(iterator.next()) // => { done: false, value: 'Klaus' }
console.log(iterator.next()) // => { done: false, value: 'Alex' }
console.log(iterator.next()) // => { done: false, value: 'Steven' }
console.log(iterator.next()) // => { done: true, value: undefined }
console.log(iterator.next()) // => { done: true, value: undefined }
Copy the code
Of course, not every iterator can iterate over the data. There are infinite iterators, but they are rare
function createIterator() {
let index = 0
return {
next() {
return {
done: false.value: index++
}
}
}
}
const iterator = createIterator()
console.log(iterator.next()) // => { done: false, value: 0 }
console.log(iterator.next()) // => { done: false, value: 1 }
console.log(iterator.next()) // => { done: false, value: 2 }
console.log(iterator.next()) // => { done: false, value: 3 }
console.log(iterator.next()) // => { done: false, value: 4 }
Copy the code
iterable
Iterables and iterators are two different kinds of objects
An object is an iterable when it implements the Iterable Protocol
The Iterable protocol requires us to implement the @@iterator method. In the actual development, the implementation is symbol. iterator
const iterableObj = {
users: ['Klaus'.'Alex'.'Steven'].Iterator refers to the @@iterable function
Iterator needs to return an iterator object
[Symbol.iterator]() {
let index = 0
return {
// The next method requires the arrow function here
// The purpose is to allow the next function to access the correct this
next: () = > ({
done: index >= this.users.length,
value: this.users[index++]
})
}
}
}
const iterator = iterableObj[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
Copy the code
Objects such as the one above that correctly implement the symbol. iterator method are called iterables
Any iterable can use for… The of method iterates through the values in the iterable,
For… Of is a syntactic sugar that calls the next method directly for traversal
const iterableObj = {
users: ['Klaus'.'Alex'.'Steven'],
[Symbol.iterator]() {
let index = 0
return {
next: () = > ({
done: index >= this.users.length,
value: this.users[index++]
})
}
}
}
for (const value of iterableObj) {
console.log(value)
}
Copy the code
This is why, by default, objects cannot use “for” directly. Of traverses because the object is not an iterable by default
const user = {
name: 'Klaus'.age: 23
}
// The following code will directly report an error - it is illegal
// Note that objects cannot use for... by default. Of traverses
/ / not for... By default, the in object can use for... In traverses the property name
for (const value of user) {
console.log(value)
}
Copy the code
In fact, a lot of the native objects that we’ve created have implemented the iterable protocol and will generate an iterator object,
String, Array, Map, Set, Arguments, and NodeList collections are all iterable by default
Usage scenarios
-
JavaScript syntax: for… Of, spread syntax, yield*, Destructuring_assignment
-
WeakMap([Iterable]), New WeakMap([Iterable]), New Set([Iterable]), New WeakSet([Iterable])
-
All (iterable), promise.race (iterable), array. from(iterable);
const iterableObj = {
users: ['Klaus'.'Alex'.'Steven'],
[Symbol.iterator]() {
let index = 0
return {
next: () = > ({
done: index >= this.users.length,
value: this.users[index++]
})
}
}
}
// Iterables can use for... Of circulation
for (const value of iterableObj) {
console.log(value)
}
/* => Klaus Alex Steven */
// Iterables can use expansion operators
console.log([...iterableObj]) // => [ 'Klaus', 'Alex', 'Steven' ]
// Iterables can use destruct operators
const [user1, user2, user3] = iterableObj
console.log(user1, user2, user3) // => Klaus Alex Steven
Copy the code
// It is important to note that objects cannot use for... by default. Of traversal
// But objects can use expansion operators and destruct operations
// This is a special processing of objects in ES9(ES2018), which is implemented internally using an iterator
const user = {
name: 'Klaus'.age: 23
}
const { name, age } = user
console.log(name, age) // => Klaus 23
console.log({... user})// => { name: 'Klaus', age: 23 }
Copy the code
const iterableObj = {
users: ['Klaus'.'Alex'.'Steven'],
[Symbol.iterator]() {
let index = 0
return {
next: () = > ({
done: index >= this.users.length,
value: this.users[index++]
})
}
}
}
// Some objects can also be passed iterators when they are created
const set = new Set(iterableObj)
console.log(set) // => Set(3) { 'Klaus', 'Alex', 'Steven' }
// Some method arguments can also accept iterables
Promise.all(iterableObj).then(res= > console.log(res)) // => [ 'Klaus', 'Alex', 'Steven' ]
/ / equivalent to the
Resolve ([promsie.resolve ('Klaus'), promsie.resolve ('Alex'), Promsie.resolve('Steven') ]).then(res => console.log(res)) */
Copy the code
An iteration of a custom class
We know that objects created by Array, Set, String, Map, etc., are all iterable by default
So we can also implement the symbol. iterator method on a class to ensure that objects created by that class are iterable by default
class School {
constructor(students) {
this.students = students
}
push(student) {
this.students.push(student)
}
// Because the School class implements the correct symbol. iterator method
// All methods created using the School class are iterable by default
// When traversing an instance of the School class, the default is to fetch the values in the corresponding students array
[Symbol.iterator]() {
let index = 0
return {
next: () = > ({
done: index >= this.students.length,
value: this.students[index++]
})
}
}
}
const school = new School(['Alex'.'Klaus'.'Steven'])
school.push('Jhon')
for (const stu of school) {
console.log(stu)
}
/* => Alex Klaus Steven Jhon */
Copy the code
The interrupt of an iterator
Iterators can break in some cases without a full iteration
- For example, the loop is interrupted by a break, continue, return, or throw
const iterableObj = {
users: ['Klaus'.'Alex'.'Steven'],
[Symbol.iterator]() {
let index = 0
return {
next: () = > ({
done: index >= this.users.length,
value: this.users[index++]
})
// If the iterator is terminated while iterating, return is executed
// The following is the default return function
// If we need to perform operations on the iterator when it terminates, we can write the corresponding logic in the return callback
// return() {
// return {
// done: true,
// value: undefined
/ /}
// }}}}for (const user of iterableObj) {
if (user === 'Alex') {
return
} else {
console.log(user)
}
}
Copy the code
- The return method is also executed during deconstruction
const iterableObj = {
users: ['Klaus'.'Alex'.'Steven'],
[Symbol.iterator]() {
let index = 0
return {
next: () = > ({
done: index >= this.users.length,
value: this.users[index++]
}),
return() {
console.log('iterator breaked')
return {
done: true.value: undefined
}
}
}
}
}
// The return method is emitted even if all values are completely deconstructed
const [user1, user2, user3] = iterableObj
console.log(user1, user2, user3)
Copy the code