迭代器
-
什么是迭代?
从一个数据集合中按照一定的顺序,不断取出数据的过程。
-
迭代和遍历的区别?
迭代强调的是依次取数据,不一定取多少;遍历强调的是把所有数据全部依次取出。
-
什么是迭代器?
对迭代过程的封装,通常为对象。
-
迭代模式是一种设计模式,用于统一迭代过程,必须拥有以下两个功能:
-
具有得到下一个数据的能力。
-
具有判断是否还有后续数据的能力。
-
JS中的迭代器
js规定,如果一个对象具有next方法,且该方法返回一个特定结构的对象,则认为该对象是个迭代器。
该对象的结构如下:
const iterator = {
next() {
return {
value: xxx, //下一个数据的值
done: boolean //是否迭代完成
}
}
}
示例:
const list = [1, 2, 3]
const iterator = {
index: 0,
next() {
return {
value: list[this.index++],
done: this.index > list.length
}
}
}
console.log(iterator.next()) //{value: 1, done: false}
console.log(iterator.next()) //{value: 2, done: false}
console.log(iterator.next()) //{value: 3, done: false}
console.log(iterator.next()) //{value: undefined, done: true}
//自动迭代
let data = iterator.next()
while(!data.done) {
data = iterator.next()
}
与遍历不同之处:迭代器内部去处理数组,外部就不用再访问数组了,从而实现对数组使用的解耦。
可迭代协议 iterator protocol
ES6规定,如果一个对象具有知名符号属性 Symbol.iterator,并且属性值是一个迭代器创建函数,则该对象是可迭代的(iterable)。
//可迭代对象
const iteratorObj = {
index: 0,
addIndex() {
this.index = this[Symbol.iterator]().next().value
},
[Symbol.iterator]() {
return {
next: () => ({
value: this.index + 1,
done: false //无限迭代
})
}
}
}
iteratorObj.addIndex()
iteratorObj.addIndex()
console.log(iteratorObj.index) //2
for-of循环
ES6新出的循环方法,专门遍历可迭代对象,是进行迭代的语法糖。
//迭代一个可迭代对象,实际上就是在不断调用该对象的 Symbol.iterator 方法的 next 方法。
const list = [1, 2, 3]
for (const item of list) {
console.log(item)
}
//输出:
//1
//2
//3
展开运算符可作用于可迭代对象,可以轻松的将可迭代对象转换为数组:
const iteratorObj = {
index: 0,
[Symbol.iterator]() {
return {
next: () => ({
value: ++this.index,
done: this.index > 3
})
}
}
}
console.log([...iteratorObj])
//输出:
//[1, 2, 3]
生成器
生成器基于迭代器而生,类似语法糖,为了更方便的书写迭代器,且拥有自己的更强大功能。
目前在 react 中重点使用了生成器。在ES7的 async/await 出来前,作为处理异步等待场景的替代方案。
- 生成器是通过构造函数 Generator 创建的对象,是个迭代器,也是可迭代对象。
- 创建生成器,必须使用生成器函数(Generator Function)。
- 使用 * 关键字书写一个生成器函数:
//方式1
function* fn1() {}
//方式2
function* fn2() {}
//对象中书写
const obj = {
*fn3() {}
}
//得到一个生成器
const generator = fn1()
console.log(generator.next()) //{value: undefined, done: true}
- 注:不可与 async 关键字同时使用,箭头函数无法作为生成器使用。
- 生成器函数内部是为了给生成器的每次迭代提供数据,每次调用生成器的 next 方法,将导致生成器函数运行到下一个
yield关键字的位置。
-
yield 关键字用来产生并传递一个迭代数据。
-
生成器函数只是生成了一个可迭代对象,函数体内的代码不执行。
// yield 关键字只能在生成器函数内部使用
function* fn() {
console.log("这段代码在调用fn函数时不会执行,对可迭代对象使用next方法时才执行")
yield 123
return 456 //return之后不会再执行迭代
yield 789
}
const generator = fn()
console.log(generator.next())
//这段代码在调用fn函数时不会执行,对可迭代对象使用next方法时才执行
//{value: 123, done: false}
console.log(generator.next()) //{value: 456, done: true}
console.log(generator.next()) //{value: undefined, done: true}
////可调用生成器的 throw 方法直接结束全部迭代并抛出错误,且可传参
console.log(generator.throw(99)) //Uncaught 99
- 相关细节:
- 函数 return 后就不会再运行了,done 的值为 true,迭代最终结果为 return 的值,可提前结束全部迭代。
- 调用生成器的 next 方法时,可传递参数(生成器特有),参数会交给 yield 表达式的返回值。
- 生成器拥有 retrun 和 throw 方法
- 怎么在一个生成器函数内调用其他生成器函数?
- 需要在生成器内部的 yield 关键字后继续加上 * 标记
function* fn1() {
yield "a"
yield "b"
}
function* fn2() {
yield* fn1()
yield 1
yield 2
}
const generator = fn2()
console.log(generator.next()) //{value: 'a', done: false}
console.log(generator.next()) //{value: 'b', done: false}
console.log(generator.next()) //{value: 1, done: false}
console.log(generator.next()) //{value: 2, done: false}
console.log(generator.next()) //{value: undefined, done: true}