生成器函数:让你的代码更优雅

2,221 阅读3分钟

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战

生成器函数基本介绍

生成器是一种特殊类型的函数。当从头到尾运行标准函数时,它最多只生成一个值。然而生成器函数会在几次运行请求中暂停,因此每次运行都可能会生成一个值。

它可以优化复杂的循环代码,如下对比可以看到生成器函数的好处:

图片.png

以上代码中包含着大量的错误处理样板代码,这样的写法看起来很丑陋。这就是生成器函数大显身手的时候了,它会让非阻塞但却丑陋的回调函数代码就会变得更优雅:

图片.png

语法

生成器函数使用function*声明。

function* 函数名(参数){函数体}

优点:

(1)生成器函数在执行时能暂停,后面又能从暂停出继续执行。

使用yield关键字可以暂停函数

(2)调用一个生成器函数,会得到生成器的迭代器对象。

使用next()方法。被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,
yield 后紧跟迭代器要返回的值。

yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。

next()-> {value:value1,done:true|false}
    value:表示本次返回的值,即yield表达式返回的值
    done:表示生成器后续是否还有yield语句,即生成器函数是否已经执行完毕并返回。
    
注意:next()方法时,如果传入了参数,那么这个参数会作为上一条直线的yield语句的返回值

简单总结:也就是说每当生成器函数生成了一个值,它都不会像普通函数一样停止执行。相反,生成器几乎从不挂起。随后,当对另一个值的请求到来后,生成器就会从上次离开的位置恢复执行。调用生成器并不会执行生成器函数,相反,它会创建一个叫作迭代器(iterator)的对象。

与普通函数的区别:

  1. 定义方式不同:使用function*声明
  2. 调用方法不同:直接调用返回的是一个迭代器对象
  3. 函数执行过程中可以通过 yield 暂停, 调用 next,函数会从 yield 的下一个语句继续执行
  4. next方法返回一个携带着生成值的对象,而该对象中包含的另一个属性done也向我们指示了生成器是否还会追加生成值。
function* test(){

  yield 'aa';

  yield 'bb';

  yield 'cc';

}

let result1 = test();

let result2 = result1.next();

let result3 = result1.next();

let result4 = result1.next();

let result5 = result1.next();

console.log('result1', result1); //Generator {  }

console.log('result2', result2); //Object { value: "aa", done: false }

console.log('result3', result3); //Object { value: "bb", done: false }

console.log('result4', result4); //Object { value: "cc", done: false }

console.log('result5', result5); //Object { value: undefined, done: true }

yield*操作符

当有两个生成器函数定义的时候,如果需要把执行权交给下一个生成器,就需要使用yield操作符将执行权交给另一个生成器。

图片.png

执行这段代码后会输出Sun Tzu、Hattori、Yoshi、Genghis Khan。

本例中,程序从WarriorGenerator跳转到一个新的NinjaGenerator生成器上,每次调用WarriorGenerator返回迭代器的next方法,都会使执行重新寻址到了NinjaGenerator上。该生成器会一直持有执行权直到无工作可做。所以我们本例中生成Sun Tzu之后紧接的是Hattori和Yoshi。仅当NinjaGenerator的工作完成后,调用原来的迭代器才会继续输出值Genghis Khan。