迭代器细节
如果对象的原型链上某个对象也实现了迭代协议,那么这个对象也是可迭代对象
迭代器的执行机制
- 调用可迭代对象实现的Symbol.iterator函数,这个工厂函数执行后会创建并返回一个新的迭代器,用于迭代其关联的可迭代对象
- 每个迭代器内部会维护一个指针,用于标记当前迭代器所处的位置
- 第一次调用迭代器的next()方法,指针会指向其关联的可迭代对象的第一个值,并返回一个迭代结果对象,包含done和value两个属性,done迭代是否结束的标志,value为当前值
- 第二次调用迭代器的next()方法,指针会移到可迭代对象的第二个值,同时返回迭代结果对象
- 继续调用迭代器的next()方法,直到指针指向迭代器的最后一个值,那么下一次调用next()方法时,指针指向的值为空,就可以个返回迭代结束的结果对象,done为true,value可以省略
实现了迭代协议的内置对象
- 字符串
- 数组
- 映射
- 集合
- arguments 对象
- NodeList 等 DOM 集合类型
迭代器维护着一个指向可迭代对象的引用,因此迭代器会阻止垃圾回收程序回收可迭代对象
接收可迭代对象的原生语言特性
- for-of 循环
- 数组解构
- 扩展运算符
- Array.from()
- 创建集合
- 创建映射
- Promise.all() 接收由Promise实例组成的可迭代对象
- Promise.race() 接收由Promise实例组成的可迭代对象
- yield* 操作符,在生成器中使用
提前终止迭代器
可以在迭代器中实现可选的reutrn()方法用于指定在迭代器关闭时执行的逻辑,执行迭代的结构在想让迭代器知道它不想让可迭代对象耗尽时,就可以关闭
迭代器,比如:
- for-of 循环通过break,continue,return或者throw提前退出
- 解构操作并未消耗所有值
const iterableObj = {
[Symbol.iterator]() {
let limit = 5
let count = 0
return {
next() {
if (count < limit) {
return {
done: false,
value: count++,
}
} else {
return {
done: true,
}
}
},
return() {
console.log('提前退出了')
// 必须要返回一个迭代结果类型的对象
return {
done: true
}
}
}
},
}
// 调用可迭代对象的Symbol.iterator方法可以获得迭代器
let iterator = iterableObj[Symbol.iterator]()
// for-of 提前退出
for(let i of iterableObj) {
if(i > 2) {
break
}
}
// 不完全解构
let [a, b, c] = iterableObj
内置语言结构在发现还有更多值可以迭代,但是不会消费这些值时,就会自动调用return()方法
如果迭代器没有被关闭,那么还可以继续迭代
const iterableObj = {
[Symbol.iterator]() {
let limit = 5
let count = 0
return {
// 让迭代器成为可迭代对象
[Symbol.iterator]() {
return this
},
next() {
if (count < limit) {
return {
done: false,
value: count++,
}
} else {
return {
done: true,
}
}
},
return() {
console.log('提前退出了')
// 迭代器指针回退
count--
// 关闭迭代器
// count = 5
return {
done: true
}
},
}
},
}
/**
* 调用可迭代对象的Symbol.iterator方法可以获得迭代器,由于迭代器也有Symbol.iterator并且执行后返回的就是可迭代对象的迭代器
* 所以同样可以被拿来迭代,因为for-of如果去直接迭代iterableObj会创建新的可迭代对象,通过闭包维护新的指针值,所以需要通过原来
* 的迭代器继续使用原来的指针值才能继续迭代
*/
let iterator = iterableObj[Symbol.iterator]()
// for-of 提前退出
for (let i of iterator) {
if (i > 2) {
break
}
console.log(i) // 0, 1, 2
}
// 如果迭代器没有被关闭,那么还可以继续迭代
for(let i of iterator) {
console.log(i) // 3, 4
}
// 直接遍历iterableObj的结果
// for-of 提前退出
for (let i of iterableObj) {
if (i > 2) {
break
}
console.log(i) // 0, 1, 2
}
// 创建了新的迭代器,所以会重新开始迭代
for(let i of iterableObj) {
console.log(i) // // 0, 1, 2,3, 4
}
如果要关闭迭代器,那么在return()方法中直接将迭代指针指改为结束条件即可,顺便提一下,数组的迭代器就是不会关闭的,所以数组可以像上面那样从离开的地方继续迭代
迭代器练习
这里会有一些迭代器封装和使用的例子,后面会持续补充
迭代器实现斐波拉契序列的产生
// 0 1 1 2 3 5 8
const Fib = {
[Symbol.iterator]() {
let n1 = 0,
n2 = 1,
index = 0
return {
[Symbol.iterator]() {
return this
},
next() {
if (index < 2) {
return {
done: false,
value: index++,
}
} else {
let temp = n1 + n2
n1 = n2
n2 = temp
return {
done: false,
value: temp,
}
}
},
}
},
}
let i = Fib[Symbol.iterator]()
let arr = []
for(let i of Fib) {
if(i > 100) {
console.log(arr) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
break
}
arr.push(i)
}
参考文献
- JavaScript高级程序设计
- 你不知道的 JavaScript(下)
- developer.mozilla.org/zh-CN/docs/…
- es6.ruanyifeng.com/#docs/itera…