关注前端小讴,浏览更多原创技术文章
生成器
- ES6 新增的构造,能够在一个函数块内暂停和复原代码执行,能够自定义迭代器和实现协程
相干代码 →
生成器根底
- 生成器的模式是一个函数,函数名称前加一个星号
*
- 能够定义函数的中央,都能够定义生成器(箭头函数除外)
function* generatorFn() {} // 生成器函数申明let gfn = function* () {} // 生成器函数表达式let foo = { *generatorFn() {}, // 生成器函数作为对象字面量办法}class Foo { *generatorFn() {} // 生成器函数作为类实例办法}class FooStatic { static *generatorFn() {} // 生成器函数作为类静态方法}
- 调用生成器函数会产生一个生成器对象,生成器对象实现了 Iterator 接口,具备
next()
办法
const g = generatorFn() // 调用生成器函数,产生生成器对象console.log(g) // generatorFn {<suspended>},生成器对象console.log(g.next) // 生成器对象具备next()办法
next()
办法的返回值相似于迭代器,有done 属性和value 属性- 函数体为空的生成器调用一次
next()
就会达到done:true
状态
console.log(g.next()) // { value: undefined, done: true },函数体为空
- 能够通过生成器函数的返回值指定
value
的返回值(默认为 undefined)
function* generatorFn2() { return 'foo'}const g2 = generatorFn2()console.log(g2.next()) // { value: 'foo', done: true }console.log(g2.next()) // { value: undefined, done: true },耗尽
- 生成器函数只会在首次调用
next()
办法后开始执行
function* generatorFn3() { console.log('生成器函数开始执行')}const g3 = generatorFn3() // 调用生成器函数,产生生成器对象(生成器函数还未执行,不打印日志)g3.next() // '生成器函数开始执行',首次调用next()办法,生成器函数开始执行,打印日志
- 生成器对象实现了 Iterable 接口,其默认迭代器是自援用的
function* generatorFn4() {}console.log(generatorFn4) // ƒ* generatorFn4() {},生成器函数const g4 = generatorFn4()console.log(g4) // generatorFn4 {<suspended>},生成器对象console.log(g4[Symbol.iterator]) // ƒ [Symbol.iterator]() { [native code] },迭代器工厂函数console.log(g4[Symbol.iterator]()) // generatorFn4 {<suspended>},迭代器console.log(g4 === g4[Symbol.iterator]()) // true,生成器对象的默认迭代器是自援用的
通过 yield 中断执行
yield
关键字能够让生成器进行和开始执行:- 生成器遇到
yield
关键字之前失常执行 - 遇到
yield
关键字后进行执行,函数作用域的状态被保留 - 进行执行的生成器函数通过生成器对象调用
next()
办法复原执行
- 生成器遇到
function* generatorFn5() { yield}let g5 = generatorFn5()console.log(g5.next()) // { value: undefined, done: false },yield生成的值console.log(g5.next()) // { value: undefined, done: true },复原执行生成的值
yield
关键字与函数的return
语句作用类似,其生成的值会呈现在next()
办法返回的对象里,但done
的状态不同:- 通过
yield
关键字退出的生成器函数会处在done:false
状态 - 通过
return
关键字退出的生成器函数会处在done:true
状态
- 通过
function* generatorFn6() { yield 'foo' yield 'bar' return 'baz'}let g6 = generatorFn6()console.log(g6.next()) // { value: 'foo', done: false },yield关键字退出console.log(g6.next()) // { value: 'bar', done: false },yield关键字退出console.log(g6.next()) // { value: 'baz', done: true },return关键字退出
- 同一个生成器函数的不同生成器对象之间没有分割,一个生成器对象上调用
next()
办法不影响其余生成器
let g7 = generatorFn6() // 生成器对象g7let g8 = generatorFn6() // 生成器对象g8console.log(g7.next()) // { value: 'foo', done: false }console.log(g8.next()) // { value: 'foo', done: false }console.log(g8.next()) // { value: 'bar', done: false }console.log(g7.next()) // { value: 'bar', done: false }
yield
关键字必须在生成器函数外部,间接位于生成器函数定义中应用,用在其余中央或嵌套的非生成器函数会报错
function* validGeneratorFn() { yield 'result'}function* invalidGeneratorFnA() { function a() { yield 'result' // SyntaxError: Unexpected string }}function* invalidGeneratorFnB() { const b = () => { yield 'result' // SyntaxError: Unexpected string }}function* invalidGeneratorFnC() { ;(() => { yield 'result' // SyntaxError: Unexpected string })()}
生成器对象作为可迭代对象
- 把生成对象当成可迭代对象
function* generatorFn7() { // 生成器函数 yield 1 yield 2 yield 3}for (const x of generatorFn7()) { // 调用生成器函数generatorFn7,generatorFn7()是生成器对象 console.log(x) /* 1 2 3 */}
- 应用生成器管制迭代循环的次数
function* nTimes(n) { while (n--) { console.log(n) yield }}for (let _ of nTimes(3)) { console.log(_) /* 2,第1次循环n undefined,第1次循环yield 1,第2次循环n undefined,第2次循环yield 0,第3次循环n undefined,第3次循环yield */}
应用 yield 实现输出和输入
除了作为函数的两头返回语句应用,
yield
关键字还能够作为函数的两头参数应用- 上一次让生成器函数暂停的
yield
关键字会接管到传给next()
办法的第一个值 - 第一次调用
next()
传入的值不会被应用,因为仅仅是为了开始执行生成器函数
- 上一次让生成器函数暂停的
function* generatorFn8() { console.log(yield) console.log(yield) console.log(yield)}let g9 = generatorFn8() // 调用生成器函数,产生生成器对象g9.next('bar') // 第一次调用next()的值不会被应用,仅作为开始执行生成器函数g9.next('baz') // 'baz',调用next()传入baz,参数作为交给同一个yield的值g9.next('qux') // 'qux',调用next()传入qux,参数作为交给同一个yield的值
yield
关键字同时用于输出和输入(与return
关键字同时应用)next()
办法没有参数,生成器函数直到遇到yield
关键字进行执行next()
办法有参数,参数作为交给同一个 yield 的值,生成器函数执行return
返回本次传入的值
function* generatorFn9() { return yield 'foo'}let g10 = generatorFn9()console.log(g10.next()) // { value: 'foo', done: false },next()没有参数,遇到yield关键字暂停执行,并计算要产生的值console.log(g10.next('bar')) // { value: 'bar', done: true },next()有参数,参数作为交给同一个yield的值,相当于return 'bar'
yield
关键字屡次应用
function* generatorFn10() { for (let i = 0; ; i++) { yield i }}let g11 = generatorFn10()console.log(g11.next()) // { value: 0, done: false }console.log(g11.next()) // { value: 1, done: false }console.log(g11.next()) // { value: 2, done: false }console.log(g11.next()) // { value: 3, done: false }
- 依据迭代次数产生相应索引
function* nTimes(n) { let i = 0 while (n--) { yield i++ }}for (let x of nTimes(3)) { console.log(x) /* 0 1 2 */}
- 应用生成器实现范畴
function* range(start, end) { while (end > start) { yield start++ }}for (const x of range(4, 7)) { console.log(x) /* 4 5 6 */}
- 应用生成器填充数组
function* zeros(n) { while (n--) { yield 0 }}console.log(zeros(8)) // zeros {<suspended>},生成器对象console.log(Array.from(zeros(8))) // [0, 0, 0, 0, 0, 0, 0, 0],生成器对象作为可迭代对象
- 应用生成器实现斐波那契数列
function* fibonacci() { let arr = [0, 1] let [prev, curr] = arr while (true) { ;[prev, curr] = [curr, prev + curr] arr.push(curr) yield arr }}function Fibonacci(n) { if (n === 1) { // 第1项 return 0 } else if (n === 2 || n === 3) { // 第2、3项 return 1 } else { // 第4项或之后 let num = 0 const fibo = fibonacci() for (let i = 3; i <= n; i++) { num = fibo.next().value } return num }}console.log(Fibonacci(8).join()) // 0,1,1,2,3,5,8,13
产生可迭代对象
- 用星号
*
加强yield
,让其可能迭代一个可迭代对象,yield*
将一个可迭代对象序列化为一连串独自产出的值
function* generatorFn11() { yield* [1, 2, 3]}let g12 = generatorFn11()for (const x of generatorFn11()) { console.log(x) /* 1 2 3 */}// 等价于function* generatorFn11() { for (const x of [1, 2, 3]) { yield x }}
yield*
的值是关联迭代器返回done:true
时value
的属性:- 对于一般迭代器,
done:true
代表迭代器耗尽,这个值是undefined
function* generatorFn12() { console.log('iterValue', yield* [1, 2, 3])}for (const x of generatorFn12()) { console.log('value', x) /* value 1 value 2 value 3 iterValue undefined*/}
- 对于一般迭代器,
- 对于生成器函数产生的迭代器,
done:true
的值是return 返回的值(没有 return 值则返回 undefined)
function* innerGeneratorFn() { yield 'foo' return 'bar'}function* outerGeneratorFn() { console.log('iterValue', yield* innerGeneratorFn())}for (const x of outerGeneratorFn()) { console.log('value', x) /* value foo iterValue bar*/}
应用 yield*
实现递归算法
- 用
yield*
实现递归,此时生成器能够产生本身
function* nTimes(n) { if (n > 0) { yield* nTimes(n - 1) // 生成器对象作为可迭代对象 yield n }}for (const x of nTimes(3)) { console.log(x) /* 1 2 3 */}
生成器作为默认迭代器
- 生成器对象实现了
Iterable
接口,生成器函数和默认迭代器被调用之后都产生迭代器
class Foo2 { // Foo既是迭代器,又是生成器函数 constructor() { this.values = [1, 2, 3] } *[Symbol.iterator]() { yield* this.values }}const f = new Foo2() // 产生可迭代的生成器对象for (const x of f) { console.log(x) /* 1 2 3 */}
提前终止生成器
- 一个实现
Iterator
接口的对象肯定有next()
办法,还有一个可选的return()
办法,生成器还有第三个办法throw()
return()
和throw()
都能够用于强制生成器进入敞开状态
function* generatorFn13() {}let g13 = generatorFn13() // 生成器对象console.log(g13.next) // ƒ next() { [native code] }console.log(g13.return) // ƒ return() { [native code] }console.log(g13.throw) // ƒ throw() { [native code] }
return
return()
办法返回种种迭代器对象的值(即 return()办法的参数)
function* generatorFn14() { yield* [1, 2, 3]}let g14 = generatorFn14()console.log(g14) // generatorFn14 {<suspended>}console.log(g14.return(5)) // {value: 5, done: true}console.log(g14) // generatorFn14 {<closed>}
- 通过
return()
办法进入敞开状态的生成器对象,后续调用next()
都会显示done:true
状态,后续提供的任何返回值都不再被存储或流传
console.log(g14.next()) // { value: undefined, done: true },曾经调用过return()console.log(g14.next()) // { value: undefined, done: true }console.log(g14.next()) // { value: undefined, done: true }
for-of
等内置语言构造会疏忽状态为done:true
的迭代器对象外部返回的值(疏忽 undefined)
let g15 = generatorFn14()for (const x of g15) { x > 1 && g15.return() // x大于1则进行生成器 console.log(x) /* 1 2 主动疏忽done:true返回的value(undefined) */}
throw
throw()
办法会在暂停的时候,将一个提供的谬误注入到生成器对象中- 如果谬误未被解决,则生成器敞开
function* generatorFn15() { yield* [1, 2, 3]}let g16 = generatorFn15()console.log(g16) // generatorFn15 {<suspended>}try { g16.throw('foo') // 注入谬误} catch (err) { console.log(err) // 'foo'}console.log(g16) // generatorFn15 {<closed>},谬误未被解决,生成器敞开
- 如果谬误在生成器函数外部解决,则生成器不会敞开,且能够复原执行;错误处理会跳过对应的
yield
function* generatorFn16() { for (const x of [1, 2, 3]) { // 谬误在生成器的try/catch块中抛出 -> (生成器对象已开始执行)在生成器外部被捕捉 // 若生成器对象未开始执行,则throw()抛出的谬误不会在生成器外部被捕捉 try { yield x // 在yield关键字处暂停执行 } catch (err) { console.log(err) // 'foo' } }}let g17 = generatorFn16()console.log(g17.next()) // { value: 1, done: false }g17.throw('foo') // 注入谬误console.log(g17.next()) // { value: 3, done: false },跳过对应的yield
总结 & 问点
- 什么是生成器?哪些函数能够定义生成器?
- 如何获取生成器对象?如何指定生成器 next()办法的 value 返回值?生成器函数什么时候开始执行?
- 如何了解“生成器对象的默认迭代器是自援用”的?
- yield 关键字在生成器中的作用是什么?其和 return 关键字的返回值有什么不同
- 同一个生成器办法生成的不同生成器对象之间有分割么?
请应用生成器函数和 yield 关键字,别离用代码实现以下性能:
- 迭代 5 次,获取每次迭代值和索引
- 获取大于 3 小于 9 的整数
- 从 1 开始,填充长度为 6 的自增数组
- 求出斐波那契数列第 20 项数字的值(从 0 算起)
yield*
的作用是什么?在一般迭代器、生成器函数产生的迭代器中,yield*
的值别离是什么?- 如何将生成器作为默认迭代器?return()和 throw()办法提前终止生成器的用法别离是什么?