因为前一篇文章在写 React 学习之常用 Redux Middleware,写到 redux-saga
中间件时,发现需要复习的迭代器与生成器的内容有点太多了,干脆单独作为一篇文章得了
迭代器与可迭代协议
迭代:按某种逻辑,依次取出下一个数据进行处理 (不需要依赖集合数据结构);它类似于 遍历
(而遍历则是从有多个数据组成的集合数据结构 (map、set、array 等数组或类数组)
中依次取出数据进行处理的过程)
迭代器 (iterator
) :用我们的话来说就是 JavaScript 语言规定,如果一个对象具有 next 方法
,且 next 方法被调用后会返回一个至少具有 value (数据的值,done 为 true 则置为 undefined)
和 done (boolean 是否迭代完毕)
属性的对象,那么我们可以称这个对象为迭代器对象
// 比如,最简结果:
// 以产生随机数为例:
const iterator = {
next() {
return {
value: Math.rondom(),
done: false
}
}
}
// 再比如,可迭代三次的迭代器:
const iteratorObj = {
total: 3,
idx: 0,
next() {
const res = {
value: this.idx > this.total ? undefined : this.idx,
done: this.idx > this.total
}
this.idx++
return res
}
}
又比如,我在 《Summary of Interview Algorithm》 一文中第 4 点有写到另外几种方案的 斐波那契数列
实现以及一些优化方案,使用迭代器实现斐波那契数列的方案如下 (那么当然,它是正向取值,其结果是不可逆的,除非去另外控制):
const fibo = {
a: 1,
b: 1,
curIdx: 1,
next() {
if (this.curIdx === 1 || this.curIdx === 2) {
this.curIdx ++
return {
value: 1,
done: false
}
}
const c = this.a + this.b
this.curIdx++
[this.a, this.b] = [this.b, c]
return {
value: c,
done: false
}
}
}
我们可以通过调用 next 方法依次取出数据,并可根据返回对象的 done 属性判定是否迭代结束
迭代器创建函数 (iterator creator):它是指一个函数,调用该函数,会返回一个迭代器,也可简称为 迭代器函数
可迭代协议
ES6 新增了 for...of
循环,该循环就是用于迭代某个对象的,因此 for...of
循环要求该对象必须是可迭代的 (该对象必须满足可迭代协议)
可迭代协议:一个对象必须拥有 知名符号属性 Symbol.iterator
,该属性必须是一个 无参的迭代器创建函数
for...of 循环原理:调用对象的 [Symbol.iterator]
方法得到一个迭代器,并调用它的 next 方法,循环判断是否迭代结束,否则取出结果的 value 属性值,执行我们写在 for...of
内部的代码:
// 比如:
for(const item of obj) {
console.log(item) // 遍历打印每一项
}
// 大概原理:
const iterator = obj[Symbol.iterator]() // 得到迭代器
let result = iterator.next()
while (!result.done) {
const item = result.value
console.log(item) // 我们写的打印每一项的代码
result = iterator.next()
}
生成器
generator
由构造函数 Generator
创建的对象,该对象是一个迭代器,同时又是一个可迭代对象(满足上面的可迭代协议)
伪代码
const generator = new Generator()
generator.next() // 拥有 next 方法
generator[Symbol.iterator] // Function 可迭代
for(const item of generator) {
// 可迭代对象,可被 for...of 循环
}
Generator
函数是 JS 引擎内部使用的构造函数,不提供给开发者
generator function
生成器函数(生成器创建函数),用于创建一个生成器。语法上为 function*
来声明函数
function* createGenerator() {
// other code...
}
// 或是
function *createGenerator() {
// other code...
}
const generator = createGenerator() // 得到一个生成器
// 所以:
generator.next // => native code Function
generator[Symbol.iterator] // => native code Function
generator.next === generator[Symbol.iterator]().next // true
生成器函数的特点
-
生成器函数调用,不会执行函数体中的函数体,而是返回一个生成器(因为生成器函数内部函数体的执行,受返回的生成器控制)
-
每当调用了返回的生成器的
next
方法,生成器函数的函数体 会从上一次yield
语句的位置(或函数体开始的位置)运行到下一个yield
语句的位置(或函数结尾)yield
关键字只能在生成器函数中使用,它表示暂停函数内部代码的执行,并返回一个当前迭代的数据;
若无下一个yield
,next 方法返回对象的 done 则会置为 true -
yield
关键字后表达式的结果会作为next 方法
返回对象的value 值
-
生成器函数最后的返回值
return "any data..."
会作为迭代器首次迭代结束时的value
值(done 初次为 true 时),后续再调用 next 方法,返回结果恒为{value: undefined, done: true}
-
生成器调用
next
方法的时候,可以传递参数,这个参数会作为生成器函数函数体上一次yield
表达式的值(生成器第一次调用 next 方法传递参数无意义,直接被忽略),如下所示:function* createGenerator() { console.log('function start...') let res = yield 1 // 第一次迭代 <next() 调用> 卡在 yield 语句,未完成赋值操作 // 第二次迭代新传的参数值会赋给 res 变量(不传则为 undefined) console.log('logger - 1', res) res = yield 2 console.log('logger - 2', res) res = yield 3 console.log('logger - 3', res) return { desc: 'function end...' } } const generator = createGenerator() // 得到生成器 generator.next(111) /* print: ‘function start...’ returns: { value: 1, done: false } */ generator.next(222) /* print: ‘logger - 1’ 222 returns: { value: 2, done: false } */ generator.next() /* print: ‘logger - 2’ undefined returns: { value: 3, done: false } */ generator.next(444) /* print: ‘logger - 3’ 444 returns: { value: { desc: 'function end...' }, done: true } */
所以,在迭代过程中,下次迭代需要上次迭代返回的结果,就可以这样处理:
// 以上面的 createGenerator 函数为例 const generator = createGenerator() // 得到生成器 let result = generator.next() // 初次调用才会有返回值 while (!result.done) { // 未迭代结束,上次迭代的返回结果传递给下一次迭代 result = generator.next(result.value) }
在 ES7
async
、await
出现之前,我们需要pro.then() => .then => .then
去进行一系列的异步操作,那么我们也可以借助生成器去完成每一步的操作:// 模拟数据请求 function getData() { return new Promise(resolve => { setTimeout(() => { resolve({ name: 'suressk', age: 25, province: 'Hubei' }) }, 2000) }) } function* task() { console.log('get data...') const data = yield getData() // value => Promise console.log('got data: ', data) } const generator = task() const { value: pro } = generator.next() // print: 'get data...' // 2 seconds later pro.then(data => generator.next(data)) // print: 'got data: ' { name: 'suressk', ... }
// 封装一个 生成器任务的运行函数: function run(generatorFunc) { const generator = generatorFunc() // 得到生成器 next() /** * 封装 generator 的 next 方法 * 调用则进行下一次迭代 */ function next(nextVal) { const { value, done } = generator.next(nextVal) if (done) return // 迭代结束 if (isPromise(value)) { value.then(data => next(data)) } else { next(value) } } } // 辅助函数,判定 obj 是不是 Promise function isPromise(obj) { return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function' } // 上面代码的 task 生成器函数则可以直接调用 run(task) // 如果 task 内部有多步 yield 截断的异步方法一样可以运行
-
生成器带有一个
throw 方法
,该方法与 next 的效果相同,唯一的区别在于:next 方法传递的参数会被返回成一个正常的值;throw 方法传递的参数是一个错误对象,而且会将此迭代器状态置为迭代结束
function* generatorFunc () { console.log('function start...') let res = yield 1 console.log('logger - 1', res) res = yield 2 console.log('logger - 2', res) res = yield 3 console.log('logger - 3', res) return 'function end...' } const generator = generatorFunc() generator.next() // 执行到 yield 1 语句停止 /** * print: 'function start...' * returns: { value: 1, done: false } */ // 若传递一个错误对象 generator.next(new Error('报错啦~')) // 执行到 yield 2 语句停止 /** * print: 'logger - 1' [错误对象('报错啦~')] * returns: { value: 2, done: false } */ generator.throw(new Error('报错啦~')) // 抛出错误,迭代结束 /** * print: [错误对象('报错啦~')] * returns: nothing... */ // 后续再调用 next() ➡️ 返回 {value: undefined, done: true}
-
生成器带有一个
return 方法
,用于直接结束生成器函数,它可以接受一个参数,作为调用它得到返回值对象的 value 属性 (不传则为 undefined)// 借用上面的生成器函数 generatorFunc const generator = generatorFunc() generator.next() // 执行到 yield 1 语句停止 generator.return() // 迭代结束 /** * returns: {value: undefined, done: true} */ generator.return('abc') /** * returns: {value: 'abc', done: true} */ // 继续调用 return 方法 generator.return('上面已经提前迭代结束了吖~') /** * returns: {value: '上面已经提前迭代结束了吖~', done: true} */
-
若需要在生成器内部调用其他生成器,若直接调用,则只是在调用的位置创建了一个生成器对象;若使用
yield 加 *
号调用,则会进入其生成器内部逐步执行function* g1() { console.log('g1 start...') let res = yield 1 console.log('g1 logger - 1', res) res = yield 2 console.log('g1 logger - 2', res) res = yield 3 console.log('g1 logger - 3', res) return 'g1 end...' } function* g2() { console.log('g2 start...') let res = yield 4 // 直接调用另一个生成器函数,这里只是得到一个生成器 // 相当于直接写了个对象在这里,无实际效果 g1() console.log('g2 logger - 1', res) res = yield 5 console.log('g2 logger - 2', res) res = yield 6 console.log('g2 logger - 3', res) return 'g2 end...' } const g = g2() g.next() // 后续调用 next,表现上看相当于 g1() 语句被直接被忽略 // ... g.next() // 后续调用 next,表现上看相当于 g1() 语句被直接被忽略 // 若在 g2 函数内部这样调用 yield* g1() function* g2() { console.log('g2 start...') let res = yield 4 // 如果这样调用,运行到这一步时会进入此生成器函数内部 // 去依次执行 g1 函数具体的代码 res = yield* g1() // g1 运行结束,这里 res 的结果为 g1 函数的返回值 console.log('g2 logger - 1', res) res = yield 5 console.log('g2 logger - 2', res) res = yield 6 console.log('g2 logger - 3', res) return 'g2 end...' }
至此,迭代器与生成器的相关内容及注意点就写到这里了,这些内容又多又绕的...