关于javascript:javascript高级程序设计学习笔记-73生成器

42次阅读

共计 7835 个字符,预计需要花费 20 分钟才能阅读完成。

关注前端小讴,浏览更多原创技术文章

生成器

  • 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() // 生成器对象 g7
let g8 = generatorFn6() // 生成器对象 g8

console.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:truevalue的属性:

    • 对于一般迭代器,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()办法提前终止生成器的用法别离是什么?

正文完
 0