生成器
在 ECMAScript2015 中新增了一种叫做生成器的函数,英文为 Generator
。引入这样一个新特性的目的就是能够在复杂的异步代码中减少回调函数嵌套所产生的的问题,从而提供更好的异步编程的解决方案。
这里先来了解一下生成器的语法结构,如下代码所示:
function* foo() {
console.log('this is generator function.')
return '前端课湛'
}
定义一个生成器函数的方式就是函数表达式定义函数方式的 function
关键字后面增加星号(*
),这样就是一个生成器函数了。
接下来,调用这个生成器函数,并且将调用后返回的结果赋值给一个变量。如下代码所示:
const result = foo()
console.log(result)
上述代码的运行结果如下:
Object [Generator] {}
从打印的结果可以看到,生成器函数的调用并没有像普通函数一样执行函数体内部的代码,而是打印了一个生成器对象。这里如果是在 Chrome 浏览器开发者工具的控制台里面,就可以看到在这个对象的原型上也存在一个迭代器的 next()
方法。如下图所示:
然后通过返回的生成器对象调用 next()
方法,看看会得到什么样子的结果,如下所示:
this is generator function.
{ value: '前端课湛', done: true }
从打印的结果可以看到,这时生成器函数的函数体内部的代码才被执行。而且 next()
方法的返回值与迭代器的 next()
方法的返回值结构是一致的。这是因为生成器对象也实现了 Iterable
接口。
如果只是这样来使用生成器函数的话,根本就看不到生成器函数的作用。因为生成器函数在实际使用的时候一定会配合 yield
关键字使用,yield
关键字的作用与 return
有些类似,但也有不同。如下代码所示:
function* foo() {
console.log('11111')
yield 100
console.log('22222')
yield 200
console.log('33333')
yield 300
}
const generator = foo()
console.log(generator.next())
上述代码的运行结果如下:
11111
{ value: 100, done: false }
从打印的结果可以看到,当执行遇到第一个 yield
关键字之后,代码就没有继续执行了。接下来,再调用一次 next() 方法的结果如下:
11111
{ value: 100, done: false }
22222
{ value: 200, done: false }
从打印的结果可以看到,这次执行到第二个 yield
关键字就停止了。
通过这样的场景可以发现一些其中的特点,生成器函数会自动返回一个生成器对象,调用这个对象的 next()
方法才能让函数的函数体开始执行,在执行的过程中如果遇到了 yield
关键字时函数的执行就会被暂停。这种特性被称为叫做惰性执行。
生成器的应用场景
了解了生成器的基本用法过后,接下来看一看生成器的应用场景。
发号器应用
首先,来实现一个比较简单的应用场景 —— 发号器。在实际业务开发过程中经常会用到自增的 ID, 而且每次调用这个 ID 都需要原有的基础上加一,使用生成器来实现这样的应用是最适合不过的了。
如下代码所示:
function* createIdMaker() {
let id = 1
while (true) {
yield id++
}
}
定义一个生成器函数,在这个函数的函数体内部定义一个 id
变量用来存储 ID,通过 while
循环来生成 ID,每次生成之后再将 ID 累加。
需要注意的是,这里不需要担心死循环的问题。因为函数的函数体在执行代码时,遇到 yield
关键字就会暂停。
实现 iterator 方法
还可以使用生成器来实现对象的 iterator()
方法,因为生成器也实现了 Iterable
接口,而且使用生成器实现 iterator()
方法要比之前的方法简单很多。
如下代码所示:
const todos = {
life: ['吃饭', '睡觉', '打豆豆'],
learn: ['语文', '数学', '外语'],
work: ['喝茶'],
[Symbol.iterator]: function* () {
const all = [].concat(this.life, this.learn, this.work)
for (const item of all) {
yield item
}
}
}
使用生成器函数实现 iterator()
方法相比于之前的代码改变有几处:
- 将
iterator()
方法定义为一个生成器函数 - 通过
for...of
循环遍历所有的可用数据 - 通过
yield
返回每一个被遍历到的对象