迭代器
小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
迭代器(iterator)是帮助我们对某个数据结构或容器对象(container,例如链表或数组)进行遍历的对象
使用迭代器遍历某一个对象的时候,我们不需要关心被遍历的对象的内部实现细节
- 其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中
- 在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等
在JS中迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol)
也就是在JS中,一个合法的迭代器是一个实现了正确的next方法的对象
这个next方法有如下的要求:
-
一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:
- done(boolean)
- 如果迭代器可以产生序列中的下一个值(没有遍历完),则为 false
- 如果迭代器已将序列迭代完毕,则为 true。
- value(any)
-
迭代器返回的任何 JavaScript 值
-
done 为 true 时可省略,但是不推荐 [如果它依然存在,即为迭代结束之后的默认返回值(也就是undefined)]
-
- done(boolean)
示例
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 }
当然,并不是每一个迭代器都可以将数据遍历完毕,存在无限的迭代器,只不过比较少见
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 }
可迭代对象
可迭代对象和迭代器是不一样的两种对象
当一个对象实现了iterable protocol协议时,它就是一个可迭代对象
而这个iterable protocol需要我们实现@@iterator方法,在实际开发中,实现的就是Symbol.iterator
const iterableObj = {
users: ['Klaus', 'Alex', 'Steven'],
// Symbol.iterator 内部指向的就是@@iterable函数
// Symbol.iterator需要返回一个迭代器对象
[Symbol.iterator]() {
let index = 0
return {
// next方法在这里需要使用箭头函数
// 目的是为了next函数内部可以正常访问到正确的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())
像上边这种正确实现了Symbol.iterator方法的对象就被称之为可迭代对象
任意一个可迭代对象,都可以使用for ... of方法遍历可迭代对象中的值,
可以认为for ... of是直接调用next方法进行遍历的一种语法糖
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)
}
这也就是为什么对象默认情况下,不能直接使用for ... of进行遍历,因为对象默认情况下并不是一个可迭代对象
const user = {
name: 'Klaus',
age: 23
}
// 以下代码会直接报错 --- 是不合法的
// 注意是 对象默认不可以使用for...of进行遍历
// 不是for...in 对象默认是可以使用for...in 进行遍历属性名的
for (const value of user) {
console.log(value)
}
事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的,
String、Array、Map、Set、arguments对象、NodeList集合在默认情况下都是一个可迭代对象
使用场景
-
JavaScript中语法: for ...of、展开语法(spread syntax)、yield*、解构赋值(Destructuring_assignment)
-
创建一些对象时: new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable])
-
一些方法的调用: Promise.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++]
})
}
}
}
// 可迭代对象可以使用for ... of循环
for (const value of iterableObj) {
console.log(value)
}
/*
=>
Klaus
Alex
Steven
*/
// 可迭代对象可以使用展开运算符
console.log([...iterableObj]) // => [ 'Klaus', 'Alex', 'Steven' ]
// 可迭代对象可以使用解构运算符
const [user1, user2, user3] = iterableObj
console.log(user1, user2, user3) // => Klaus Alex Steven
// 需要注意的是,虽然对象默认情况下不可以使用for...of遍历
// 但是对象是可以使用展开运算符和解构操作的
// 这是ES9(ES2018)中对对象进行的特殊处理,其内部使用的并不是迭代器进行实现
const user = {
name: 'Klaus',
age: 23
}
const { name, age } = user
console.log(name, age) // => Klaus 23
console.log({...user}) // => { name: 'Klaus', age: 23 }
const iterableObj = {
users: ['Klaus', 'Alex', 'Steven'],
[Symbol.iterator]() {
let index = 0
return {
next: () => ({
done: index >= this.users.length,
value: this.users[index++]
})
}
}
}
// 某些对象在创建的时候,也可以传入迭代器作为创建时候的参数
const set = new Set(iterableObj)
console.log(set) // => Set(3) { 'Klaus', 'Alex', 'Steven' }
// 某些方法的参数也可以接收可迭代对象
Promise.all(iterableObj).then(res => console.log(res)) // => [ 'Klaus', 'Alex', 'Steven' ]
// 等价于
/*
// 基本数据类型会使用Promise.resolve方法进行包裹
Promise.all([
Promsie.resolve('Klaus'),
Promsie.resolve('Alex'),
Promsie.resolve('Steven')
]).then(res => console.log(res))
*/
自定义类的迭代
我们知道Array、Set、String、Map等类创建出来的对象默认都是可迭代对象
因此我们也可以在类上实现Symbol.iterator方法,从而确保该类所创建出来的对象默认是可迭代的
class School {
constructor(students) {
this.students = students
}
push(student) {
this.students.push(student)
}
// 因为School类实现了正确的Symbol.iterator方法
// 所以使用School类创建出来的方法默认都是可迭代的
// 对School类的实例进行遍历的时候,默认会依次取出对应students数组中的各个值
[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
*/
迭代器的中断
迭代器在某些情况下会在没有完全迭代的情况下中断
- 比如遍历的过程中通过break、continue、return、throw中断了循环操作
const iterableObj = {
users: ['Klaus', 'Alex', 'Steven'],
[Symbol.iterator]() {
let index = 0
return {
next: () => ({
done: index >= this.users.length,
value: this.users[index++]
})
// 如果迭代器在遍历的时候被终止了,那么会执行return函数
// 以下是默认的return函数
// 如果我们需要在iterator在遍历被终止的时候执行某些操作可以在return回调中编写对应的逻辑
// return() {
// return {
// done: true,
// value: undefined
// }
// }
}
}
}
for (const user of iterableObj) {
if (user === 'Alex') {
return
} else {
console.log(user)
}
}
- 在解构的时候, return方法也会被执行
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
}
}
}
}
}
// 即使所有的值全部被解构完全了,return方法依旧会被触发
const [user1, user2, user3] = iterableObj
console.log(user1, user2, user3)