Generator实质

来源: <http://blog.liuwanlin.info/generatorshi-zhi/>

 superlin •  September 15, 2015 • 1 Comment

ES6里面最有意思,也是最有用的除了Promise之外就是Generator了,关于Generator的规范也是看了有一段时间了,今天想起来还是写一写这部分的内容。

用一句简单的话来概括Generator的核心技术的话,那就是:将EC保存起来,每次执行代码的时候恢复EC,这样一个函数里面的代码就可以一小段一小段的去执行了。而且作用域链也会被保存起来了,所以JS原有的闭包和词法作用域也是依旧不变的。

具体我们通过ES6规范来看一看其中奥妙吧。

Generator

generator函数执行的时候,会进行如下动作:

  1. 创建一个VO,与当前EC(Execution Context,以下简称EC)的作用域链组成新的作用域链
  2. 创建一个generator对象,其有如下值: 
    • Scope:新建的作用域链
    • Code:generator function内部的代码
    • ExecutionContext:EC,目前值为null
    • State:”newborn”
    • Handler:默认的generator的处理器

这里可以看到,Generator函数的执行,函数体内部的代码是不会动的,而是创建一个generator对象,将代码存入其中,并给予相关的上下文

yield的行为

当执行到yield e时:

  1. 计算出表达式e的值
  2. 获取当前的EC,并从中获取currentGenerator,也就是yield所在的generator对象
  3. 使这个generator对象的ExecutionContext指向当前EC,并将其state修改为suspended
  4. 从EC栈弹出当前的EC
  5. 返回(normal, 1中的结果值, null)

可以看到,yield本身会先获得表达式的值后,将EC从栈顶弹出,交予generator对象。最后会返回一个结构,其含有三个属性,分别为运行结果、计算的结果值和null,Resume在检测到这个结构后,将停止代码的运行

这里yield之后将会返回到当前函数之外,作用域将发生改变,EC栈中的栈顶也会随之改变。而我们在generator function的函数体内部的这个EC,在下一次回来继续执行时依旧需要使用,所以这里就要交给generator对象代为管理一下,等下次回来,将重新压入EC栈的栈顶

return的行为

当执行到return e时:

  1. 计算出表达式e的值
  2. 获取当前EC,并从中获取currentGenerator,也就是return所在的generator对象
  3. 将这个generator对象的状态修改为closed
  4. 创建一个class为StopIteration的新对象,并使其value属性为1中计算的结果值
  5. throw这个对象

return也是一样,它同样需要先计算出表达式的值。但之后它获得了generator对象并不是为了做EC栈的维护,而是为了修改generator对象的状态

私有属性

  • prototype:Object.prototype
  • code:generator函数的函数体
  • ExecutionContext:内部代码运行使用的EC
  • Scope:作用域链
  • Handler:标准的generator句柄
  • State:newborn、executing、suspended、closed
  • Send:看内部方法部分
  • Throw:看内部方法部分
  • Close:看内部方法部分

外部接口

next

  1. 如果this指向的不是generator对象,抛异常
  2. 调用this.send,传入一个undefined
  3. 返回结果

调用私有send方法

send

send方法允许指定一个值,作为上一次yield的返回值

  1. 如果this指向的不是generator对象,抛异常
  2. 调用this.send,传入当前第一个参数
  3. 返回结果

同样是调用私有send方法,不过传入了参数

throw

  1. 如果this指向的不是generator对象,抛异常
  2. 调用this.throw,传入当前第一个参数
  3. 返回结果

close

调用close方法可以直接以当前的value作为Generator的返回值

  1. 如果this指向的不是generator对象,抛异常
  2. 调用this.close,不传入任何参数
  3. 返回结果

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方法

  1. 判断generator对象的state,如果是executing或者closed,就报错。已经在运行了不能重复运行,已经关闭的自然不能运行
  2. 如果state为newborn 
    1. 将判断传入的参数是否为undefined(外部接口next传入undefined,send则传入给的参数)。这里如果不是undefined,就报错。也就是说刚创建的generator对象不能调用含有参数的send外部接口。
    2. 创建一个新的EC,这个新的EC的currentGenerator执行这个generator对象,其作用域链为这个generator对象的作用域链
    3. 将这个EC压入EC栈中
    4. 执行generator中的代码,并返回或得到的结果
  3. 能到这,说明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

  1. 获取generator对象的state,如果为executing或者closed,无法抛异常,报错
  2. 如果state为newborn,那么state修改为closed,code修改为null,返回一个包含传入参数的异常
  3. 到这里说明state为suspended,修改state为executing,然后通过Resume(generator.ExectionContext, throw, 传入的参数)获得结果,并返回
  4. 这里如果是suspended,那么需要通过Resume,且completionType为throw来进行抛错

close

  1. 获取generator对象的state,如果state为executing,那说明代码正在运行,为了防止出现错误,禁止close。
  2. 如果state已经是closed了,那直接return就好
  3. 如果state为newborn,state修改为closed,code修改为null,然后返回(normal, undefined, null)
  4. 如果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)

  1. 将这个传入的EC(generator的ExecutionContext)压入到EC栈中
  2. 从EC通过currentGenerator获取单签generator对象
  3. 设置当前作用域链为当前generator对象的作用域链
  4. 继续执行代码,并根据completionType做相应的处理

参考

ES6 Generator

 

Generator实质的更多相关文章

  1. Generator函数异步应用

    转载请注明出处: Generator函数异步应用 上一篇文章详细的介绍了Generator函数的语法,这篇文章来说一下如何使用Generator函数来实现异步编程. 或许用Generator函数来实现 ...

  2. JavaScript中的Generator函数

    1. 简介 Generator函数时ES6提供的一种异步编程解决方案.Generator语法行为和普通函数完全不同,我们可以把Generator理解为一个包含了多个内部状态的状态机. 执行Genera ...

  3. async/await 与 generator、co 的对比

    之前写过一个分批预加载资源的插件,其实质便是串行执行异步,使用的方法是generator + promise -- 前几天写了一个爬虫,抓取页面的n个页面的音频资源,其也是串行执行异步,但是在使用的a ...

  4. 15.Generator 函数的语法

    Generator 函数的语法 Generator 函数的语法 简介 基本概念 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同.本章详细介绍 Generat ...

  5. ES6的新特性(16)——Generator 函数的语法

    Generator 函数的语法 简介 基本概念 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同.本章详细介绍 Generator 函数的语法和 API,它的 ...

  6. Generator 函数的语法

    简介 § ⇧ 基本概念 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同.本章详细介绍 Generator 函数的语法和 API,它的异步编程应用请看< ...

  7. Python的程序结构[7] -> 生成器/Generator -> 生成器浅析

    生成器 / Generator 目录 关于生成器 生成器与迭代器 生成器的建立 通过迭代生成器获取值 生成器的 close 方法 生成器的 send 方法 生成器的 throw 方法 空生成器的检测方 ...

  8. 浅谈Generator和Promise原理及实现

    Generator 熟悉ES6语法的同学们肯定对Generator(生成器)函数不陌生,这是一个化异步为同步的利器. 栗子: function* abc() { let count = 0; whil ...

  9. TypeScript 迭代器(iterator)和生成器(generator)

    ⒈迭代器(iterator) 1.可迭代性 当一个对象实现了Symbol.iterator属性时,我们认为它是可迭代的. 一些内置的类型如 Array,Map,Set,String,Int32Arra ...

随机推荐

  1. 服务命令只支持基本的LSB操作(启动、停止、重新启动、尝试重启、重新加载、强制重新加载、状态)。对于其他操作,请尝试使用systemctl。

    The service command supports only basic LSB actions (start, stop, restart, try-restart, reload, forc ...

  2. Hadoop==zookeeper

    Zookeeper 每一个专业的技术总可以在生活中找到相应的实例,就比如说zookeeper,攘其外必先安其内就很好的解释了zookeeper,Hadoop集群的组件中的很多在学习的时候都会觉得每一个 ...

  3. linux文件操作篇 (三) 文件状态和操作属性

    #include <sys/stat.h>   int fstat(int fildes, struct stat *buf); 获取文件信息  int lstat(const char* ...

  4. R语言绘图:ROC曲线图

    使用pROC包绘制ROC曲线 #####***绘制ROC曲线***##### library("pROC") N <- dim(data2)[1] #数据长度 set.see ...

  5. C# 面试题 (一)

    一.C# 理论 1.1.简述 private. protected. public. internal.protected internal 访问修饰符和访问权限 private : 私有成员, 在类 ...

  6. Java线程和多线程(九)——死锁

    Java中的死锁指的就是一种多于两个线程永远阻塞的特殊状况.Java中的死锁状态至少需要多于两个线程以及资源的时候才会产生.这里,我写了一个产生死锁的程序,并且讲下如何分析死锁. 首先来看一下产生死锁 ...

  7. Mysql 启动运行

    MYSQL默认端口:3306用户: root   也可自己添加用户启动数据库服务名: MYSQL (在安装的时候会设置) 在开始菜单栏->附件->右键命令提示符->以管理员身份运行: ...

  8. Linux 下 PHP 扩展Soap 编译安装

    1.进入 PHP 的软件包 pdo 扩展目录中(注:不是 PHP 安装目录) [root@tester /]# /home/tdweb/php-5.4.34/ext/soap 执行 phpize 命令 ...

  9. Mybatis快速入门指南

    简介 当下越来越多的企业项目架构中,在持久层部分,抛弃了Hibernate框架,而选用Mybatis框架取而代之,旨在更加深入细致的控制和数据库的交互. MyBatis 本是apache的一个开源项目 ...

  10. VS2010使用NuGet程序包管理器

    使用C#过程中经常需要使用一些扩展包,例如sqlite,json解析等. VS2010自带了一个扩展管理器,里面可以下载到AStyle,Visual Assit等有用的插件. VS2010中点击[工具 ...