1 Star 4 Fork 1

dony / 学习笔记

Create your Gitee Account
Explore and code with more than 8 million developers,Free private repositories !:)
Sign up
This repository doesn't specify license. Please pay attention to the specific project description and its upstream code dependency when using it.
Clone or Download
Generator (生成器).md 6.18 KB
Copy Edit Web IDE Raw Blame History
dony authored 2022-03-06 08:27 . Generator(生成器)

Generator (生成器)

ES2015规范提出一种跟迭代器(iterator)密切相关的写法,这就是生成器,它也叫做半协程。生成器是一种比普通函数更宽泛的函数结构,它可以有多个入口点。普通函数只有一个入口点,也就是函数的开头,每次执行函数,它都会从头执行,但生成器却不一定要从开头执行,而是可以从上次暂停的那个地方接着往下执行(生成器遇到yield语句会暂停),它特别适合用来实现迭代器,生成器函数(generator function)所返回的生成器对象(generator object),本身既是iterator,也是iterable。

Generator语法

生成器函数(generator function)需要用function *来定义,也就是在function关键字后面加一个型号:

function *myGenerator(){
	//生成器的主体部分
}

调用生成函数时,程序并不会想调用普通函数那样,立刻执行其中的代码,而是会让这种函数返回一个生成器对象,这样的对象既是iterator,又是iterable。另外还有个重要的地方,就是在生成器对象上面调用next(),会让生成器从头执行或从上次暂停的地方继续往下执行,如果它遇到yield指令,那么会再度暂停,如果它遇到return指令或到达函数尾部,那么这个生成器就算执行完毕了。

简单的生成器函数

这里实现一个简单的生成器函数,也就是**fruitGenerator()**函数,让它生成两种水果的名字,并指出这两种水果是在哪个季节成熟的:

function * fruitGenerator() {
  	yield 'peach'
  	yield 'watermelon'
  	return 'summer'
}

const fruitGeneratorObj = fruitGenerator()
console.log(fruitGeneratorObj.next())
console.log(fruitGeneratorObj.next())
console.log(fruitGeneratorObj.next())

打印结果如下:

{ value: 'peach', done: false }
{ value: 'watermelon', done: false }
{ value: 'summer', done: true }

简单地解释执行过程:

1,第一次出发fruitGenerator.next()的时候,生成器会从头开始执行,直至遇见yield命令为止,此时生成会暂停,并把'peach'这个值返回给调用方。

2,第二次出发fruitGenerator.next()的时候,生成器会从早前暂停的地方继续执行,直至遇到yield命令为止,此时生成器又会暂停,并把'watermelon'这个值返回给调用方。

3,最后一次出发fruitGenerator.next()的时候,生成器会从早前暂停的地方继续执行,但这次它遇到的是return语句,因此这个生成器就终止执行,并返回'summer'这个值,同时,还会把表示执行结果的那个对象里面的done属性设为true。

由于生成器对象本身也是iterable,因此可以用在for ... of 结构里面。例如:

for (const fruit of fruitGenerator()) {
  	console.log(fruit)
}

打印结果:

peach
watermelon

由于summer不是由生成器函数的代码通过yield命令给出的,而是通过return语句返回,所以for ... of结构不会把它当作生成器对象所生成的元素。这个值只用说明,生成器执行完毕时,所返回的结构时summer。

生成器对象的执行逻辑

生成器对象不仅具备普通的迭代器所具备的能力,而且还可以通过next()方法接收参数(对于生成器来说,迭代器协议中的这个next()方法,可以带参数,也可以不带)。如果带参数,那么就可以把调用方在触发next()方法时所传入的参数,赋给yield指令左侧的变量。可以创建一个简单的生成器函数来演示下:

function * twoWayGenerator () {
  	const what = yield null
    yield 'Hello ' + what
}

const twoWay = twoWayGenerator();
twoWay.next()
console.log(twoWay.next('world'))

这段代码执行后,会打印出 Hello world

1,首次出发next() 方法,会把生成器推进到第一条yield语句那里,然后令其暂停。

2,第二次出发next() 方法,会让生成器从暂停的地方继续往下运行,但由于触发的时候传入了参数'world',因此,生成器会收到我们传入的这个参数,于是,它先把该值赋给上一条yield语句左侧的那个变量,也就是what,然后再向下推进。这次它会停到第二条yield语句那里,把'Hello'这个字符串与what变量的内容(即'world')相拼接,并将拼出来的结果返回给调用方。于是,控制台上面打印出的就是 Hello world

生成器对象还支持两种方法,也就是throw()与return(),在使用生成器的过程中,不一定非得调用这两个方法。throw() 方法会把异常抛给生成器,让人觉得这个异常好像出现在生成器上次执行到的yield命令那里,throw() 方法返回的是一个表示迭代结果的对象,其中包含donevalue属性。return() ** 方法会迫使生成器终止,并返回 { done: true, vallue: returnArgument }形式的对象,其中的returnArgument**,就是在调用**return()**方法时传入的那个值。

下面这段代码演示了这两个方法的功能:

function * twoWayGenerator() {
  	try {
      	const what = yield null
        yield 'Hello ' + what
    }catch(err) {
      	yield 'Hello error: ' + err.message
    }
}

console.log('Using throw():')
const twoWayException = twoWayGenerator()
twoWayException.next()
console.log(twoWayException.throw(new Error("Boom! ")))

console.log('Using return():')
const twoWayReturn = twoWayGenerator()
console.log(twoWayReturn.return('myReturnValue'))

结果打印如下:

Using throw():
{value: 'Hello error: Boom! ', done: false}
Using return():
{value: 'myReturnValue', done: true}

这里通过throw() 方法给生成器注入异常,会把它从上次暂停的地方继续执行,并且刚一执行就立刻发生异常,被try catch捕获下来。接着我们有制作了一个生成器对象,然后调用**return()**方法,这会让生成器停止执行,并通过return()方法传入的值,放在一个表示迭代结果的对象里面,并返回给我们。

Comment ( 0 )

Sign in to post a comment

1
https://gitee.com/dony1122/note.git
git@gitee.com:dony1122/note.git
dony1122
note
学习笔记
master

Search