Generator实质
来源: <http://blog.liuwanlin.info/generatorshi-zhi/>
superlin ? September 15, 2015 ? 1 Comment
ES6里面最有意思,也是最有用的除了Promise之外就是Generator
了,关于Generator
的规范也是看了有一段时间了,今天想起来还是写一写这部分的内容。
用一句简单的话来概括Generator
的核心技术的话,那就是:将EC保存起来,每次执行代码的时候恢复EC,这样一个函数里面的代码就可以一小段一小段的去执行了。而且作用域链也会被保存起来了,所以JS原有的闭包和词法作用域也是依旧不变的。
具体我们通过ES6规范来看一看其中奥妙吧。
Generator
generator函数执行的时候,会进行如下动作:
- 创建一个VO,与当前EC(Execution Context,以下简称EC)的作用域链组成新的作用域链
- 创建一个generator对象,其有如下值:
- Scope:新建的作用域链
- Code:generator function内部的代码
- ExecutionContext:EC,目前值为null
- State:”newborn”
- Handler:默认的generator的处理器
这里可以看到,Generator函数的执行,函数体内部的代码是不会动的,而是创建一个generator对象,将代码存入其中,并给予相关的上下文
yield的行为
当执行到yield e时:
- 计算出表达式e的值
- 获取当前的EC,并从中获取currentGenerator,也就是yield所在的generator对象
- 使这个generator对象的ExecutionContext指向当前EC,并将其state修改为suspended
- 从EC栈弹出当前的EC
- 返回(normal, 1中的结果值, null)
可以看到,yield本身会先获得表达式的值后,将EC从栈顶弹出,交予generator对象。最后会返回一个结构,其含有三个属性,分别为运行结果、计算的结果值和null,Resume在检测到这个结构后,将停止代码的运行
这里yield之后将会返回到当前函数之外,作用域将发生改变,EC栈中的栈顶也会随之改变。而我们在generator function的函数体内部的这个EC,在下一次回来继续执行时依旧需要使用,所以这里就要交给generator对象代为管理一下,等下次回来,将重新压入EC栈的栈顶
return的行为
当执行到return e时:
- 计算出表达式e的值
- 获取当前EC,并从中获取currentGenerator,也就是return所在的generator对象
- 将这个generator对象的状态修改为closed
- 创建一个class为StopIteration的新对象,并使其value属性为1中计算的结果值
- throw这个对象
return也是一样,它同样需要先计算出表达式的值。但之后它获得了generator对象并不是为了做EC栈的维护,而是为了修改generator对象的状态
私有属性
- prototype:Object.prototype
- code:generator函数的函数体
- ExecutionContext:内部代码运行使用的EC
- Scope:作用域链
- Handler:标准的generator句柄
- State:newborn、executing、suspended、closed
- Send:看内部方法部分
- Throw:看内部方法部分
- Close:看内部方法部分
外部接口
next
- 如果this指向的不是generator对象,抛异常
- 调用this.send,传入一个undefined
- 返回结果
调用私有send方法
send
send方法允许指定一个值,作为上一次yield的返回值
- 如果this指向的不是generator对象,抛异常
- 调用this.send,传入当前第一个参数
- 返回结果
同样是调用私有send方法,不过传入了参数
throw
- 如果this指向的不是generator对象,抛异常
- 调用this.throw,传入当前第一个参数
- 返回结果
close
调用close方法可以直接以当前的value作为Generator的返回值
- 如果this指向的不是generator对象,抛异常
- 调用this.close,不传入任何参数
- 返回结果
iterate
由于每个generator对象都是一个iterator对象,直接return this就可以了
小结
接口都是内部方法的一层封装,可以看到next和send实际上都是send内部方法的包装
状态定义
- newborn:Code不为null,EC为null
- executing:Code为null,EC不为null,且generator对象的EC为当前EC
- suspended:Code为null,EC不为null,且generator对象的EC不为当前EC
- closed:Code为null,EC为null
调用了generator function后,生成的generator对象状态即为newborn。也就表明当前generator对象刚刚新建,还没有运行里面的任何代码。同时可以看到EC为null,说明内部运行时的EC并不存在
调用了send方法后,状态会修改为executing,send方法会使用Resume去执行代码,直到遇到yield或者return。遇到yield后,代码停止继续执行,状态修改为suspended,等待下次send。遇到return后,状态将被修改为closed,说明执行完毕。
当然也可以通过close方法,手动修改状态为closed
内部方法
send方法
- 判断generator对象的state,如果是executing或者closed,就报错。已经在运行了不能重复运行,已经关闭的自然不能运行
- 如果state为newborn
- 将判断传入的参数是否为undefined(外部接口next传入undefined,send则传入给的参数)。这里如果不是undefined,就报错。也就是说刚创建的generator对象不能调用含有参数的send外部接口。
- 创建一个新的EC,这个新的EC的currentGenerator执行这个generator对象,其作用域链为这个generator对象的作用域链
- 将这个EC压入EC栈中
- 执行generator中的代码,并返回或得到的结果
- 能到这,说明state只能是suspended。将state修改为executing,通过Resume(generator的ExecutionContext, normal, 传入的参数)获取结果并返回
generator对象的next和send方法的真正实现,其只处理newborn和suspended状态
在newborn状态下,这个generator内部的代码还没有被执行,其内部代码执行时的EC也没有被创建。所以需要创建一个EC并压入EC栈中
而state为suspended就没有这个EC初始化的过程了,内部代码执行时的EC已经在generator的ExecutionContext上了,所以只要修改状态为executing,然后使用Resume执行代码就好
throw
- 获取generator对象的state,如果为executing或者closed,无法抛异常,报错
- 如果state为newborn,那么state修改为closed,code修改为null,返回一个包含传入参数的异常
- 到这里说明state为suspended,修改state为executing,然后通过Resume(generator.ExectionContext, throw, 传入的参数)获得结果,并返回
- 这里如果是suspended,那么需要通过Resume,且completionType为throw来进行抛错
close
- 获取generator对象的state,如果state为executing,那说明代码正在运行,为了防止出现错误,禁止close。
- 如果state已经是closed了,那直接return就好
- 如果state为newborn,state修改为closed,code修改为null,然后返回(normal, undefined, null)
- 如果state为suspended,将其修改为executing,通过Resume(generator.ExecutionContext, return, undefined)获得结果,然后修改状态为closed,返回Resume获得的结果
调用close方法可以直接以当前的value作为Generator的返回值,当为newborn时,还没有value,自然是undeinfed。而如果是suspended,就有value了,那么就需要通过Resume,且completionType为return来立即返回
Resume(EC, completionType, V)
- 将这个传入的EC(generator的ExecutionContext)压入到EC栈中
- 从EC通过currentGenerator获取单签generator对象
- 设置当前作用域链为当前generator对象的作用域链
- 继续执行代码,并根据completionType做相应的处理