js的Generator

Generator

主要参考: https://www.cnblogs.com/rogerwu/p/10764046.html

Generator 函数的概念

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)

 

Generator函数的特征

1、function关键字与函数名之间有一个星号 * (推荐紧挨着function关键字)

2、函数体内使用 yield 表达式,定义不同的内部状态 (可以有多个yield)

3、直接调用 Generator函数并不会执行,也不会返回运行结果,而是返回一个遍历器对象(Iterator Object)

依次调用遍历器对象的next方法,遍历 Generator函数内部的每一个状态

{
  // 传统函数
  function foo() {
    return 'hello world'
  }
​
  foo()   // 'hello world',一旦调用立即执行
​
​
  // Generator函数
  function* generator() {
    yield 'status one'         // yield 表达式是暂停执行的标记  
    return 'hello world'
  }
​
  let iterator = generator()   // 调用 Generator函数,函数并没有执行,返回的是一个Iterator对象
  iterator.next()              // {value: "status one", done: false},value 表示返回值,done 表示遍历还没有结束
  iterator.next()              // {value: "hello world", done: true},value 表示返回值,done 表示遍历结束
}
{
  function* gen() {
    yield 'hello'
    yield 'world'
    return 'ending'
  }
​
  let it = gen()
​
  it.next()   // {value: "hello", done: false}
  it.next()   // {value: "world", done: false}
  it.next()   // {value: "ending", done: true}
  it.next()   // {value: undefined, done: true}
}

 

 

yield 表达式

yield命令是异步两个阶段的分界线

它表示执行到此处,执行权将交给其他协程

yield 表达式只能用在 Generator 函数里面,用在其它地方都会报错
{
  function* demo() {
    console.log('Hello' + yield); // SyntaxError
    console.log('Hello' + yield 123); // SyntaxError
  
    console.log('Hello' + (yield)); // OK
    console.log('Hello' + (yield 123)); // OK
  }
}

  

yield 表达式用作参数或放在赋值表达式的右边,可以不加括号
{
  function* demo() {
    foo(yield 'a', yield 'b'); // OK
    let input = yield; // OK
  }
}

  

yield 表达式和return语句的区别

相似

都能返回紧跟在语句后面的那个表达式的值

区别:

return 语句不具备记忆位置的功能

每次遇到 yield,函数就暂停执行,下一次再从该位置继续向后执行

return 语句一个函数只能执行一次

而在 Generator 函数中可以有任意多个 yield

 

 

yield表达式

如果在 Generator 函数里面调用另一个 Generator 函数,默认情况下是没有效果的

没有效果的案例

只返回了自身的两个状态值

{
  function* foo() {
    yield 'aaa'
    yield 'bbb'
  }
​
  function* bar() {
    foo()
    yield 'ccc'
    yield 'ddd'
  }
​
  let iterator = bar()
​
  for(let value of iterator) {
    console.log(value)
  }
​
  // ccc
  // ddd
​
}

  

yield 表达式使用
{
  function* foo() {
    yield 'aaa'
    yield 'bbb'
  }
​
  function* bar() {
    yield* foo()      // 在bar函数中 **执行** foo函数
    yield 'ccc'
    yield 'ddd'
  }
​
  let iterator = bar()
​
  for(let value of iterator) {
    console.log(value)
  }
​
  // aaa
  // bbb
  // ccc
  // ddd
}

  

 

next() 方法的参数

field表达式本身没有返回值,或者说总是返回undefined

next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值

案例解析

第一次it.next()

1、let result = undefined // 变量声明提前

2、yield [3+5+6] // 返回yield表达式的值并暂停

3、result = ... // 由于被暂停,无法给result赋值

第二次it.next()

1、result = ... // 将第二次next的参数赋值给result,紧接着上一个yield 的下一行执行

2、yield result // 返回yield的值并暂停

{
  function* gen() {
    let result = yield 3 + 5 + 6
​
    yield result
  }
​
  let it = gen()
  console.log(it.next())      // {value: 14, done: false}
  console.log(it.next())      // undefined    {value: undefined, done: false}
}

  

第一次调用next()中的参数无效案例

next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的

{
  function* gen() {
    let result = yield 3 + 5 + 6
    console.log(result)
    yield result
  }
​
  let it = gen()
  console.log(it.next(10))      // {value: 14, done: false}
  console.log(it.next(3))      // 3    {value: 3, done: false}
}

  

yield 表达式如果用在另一个表达式中,必须放在圆括号里面
{
  function* gen(x) {
    let y = 2 * (yield (x + 1))   // 注意:yield 表达式如果用在另一个表达式中,必须放在圆括号里面
    let z = yield (y / 3)
    return x + y + z
  }
​
  let it = gen(5)
​
  console.log(it.next())  // 正常的运算应该是先执行圆括号内的计算,再去乘以2,由于圆括号内被 yield 返回 5 + 1 的结果并暂停,所以返回{value: 6, done: false}
  console.log(it.next(9))  // 上次是在圆括号内部暂停的,所以第二次调用 next方法应该从圆括号里面开始,就变成了 let y = 2 * (9),y被赋值为18,所以第二次返回的应该是 18/3的结果 {value: 6, done: false}
  console.log(it.next(2))  // 参数2被赋值给了 z,最终 x + y + z = 5 + 18 + 2 = 25,返回 {value: 25, done: true}
}
 

  

与 Iterator 接口的关系

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性

简言之,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”

{
  let obj = {}
​
  function* gen() {
    yield 4
    yield 5
    yield 6
  }
​
  // 为obj添加Symbol.iterator属性
  // >> 传统对象没有原生部署 Iterator接口,不能使用 for...of 和 扩展运算符
  // >> 现在通过给对象添加 Symbol.iterator 属性和对应的遍历器生成函数,就可以使用了
  obj[Symbol.iterator] = gen
​
  for(let value of obj) {
    console.log(value)
  }
  // 4
  // 5
  // 6
​
  // ...obj的赋值方式可以避免改变原obj中的值
  console.log([...obj])    // [4, 5, 6]
}

  

 

for...of循环

由于 Generator 函数运行时生成的是一个 Iterator 对象,因此,可以直接使用 for...of 循环遍历,且此时无需再调用 next() 方法

注意:

一旦 next() 方法的返回对象的 done 属性为 true,for...of 循环就会终止,且不包含该返回对象

{
  function* gen() {
    yield 1
    yield 2
    yield 3
    yield 4
    return 5
  }
  
  for(let item of gen()) {
    console.log(item)
  }
  
  // 1 2 3 4
}

  

 

Generator的return 方法

Generator 函数返回的遍历器对象,还有一个 return 方法

可以返回给定的值(若没有提供参数,则返回值的value属性为 undefined),并且终结遍历 Generator 函数

{
  function* gen() {
    yield 1
    yield 2
    yield 3
  }
​
  let it = gen()
​
  it.next()             // {value: 1, done: false}
  it.return('ending')   // {value: "ending", done: true}
  it.next()             // {value: undefined, done: true}
}

  

 

应用案例

参照:https://www.cnblogs.com/rogerwu/p/10764046.html 网页最后的案例

 

posted @ 2020-12-23 14:49  minnersun  阅读(276)  评论(0编辑  收藏  举报