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. DevOps - 配置管理 - Ansible

    http://www.zsythink.net/archives/category/运维相关/ansible/

  2. Asp.Net Core 使用Docker进行容器化部署(一)

    前几篇文章介绍了Liunx下的环境搭建,今天来分享一下.Net Core在Liunx上的部署. 我采用的方案是使用Dokcer作为运行虚拟机,Nginx作为Http服务器来进行反向代理,你可以理解为D ...

  3. Java : java基础(1)

    java编译器有常亮优化机制,如果是常量的计算,会直接判断常量计算结果的取值范围,如果是变量,则没办法判断计算取值范围,编译会异常(如两个byte类型的变量相加). java中的常量指的是用 stat ...

  4. 关于Vue的Render的讲解

    首先我们传统的对于DOM的操作基本上都是通过js直接的获取一个节点,然后对DOM进行增加或者是删除.而Vue的Render这个函数是通过js虚拟的添加dom节点,然后虚拟的添加到html节点上去. 算 ...

  5. MongoDB在单机上搭建分片副本集群(windows),版本二

    配置可以参考前面一篇 https://www.cnblogs.com/a-horse-mosaic/p/9284010.html 副本集是一组服务器,其中有一个主服务器(primary),用于处理客户 ...

  6. python中的字符串(str)操作

    字符串是python中数据类型.一般就单引号(‘’)或双引号(“”)引起来的内容就是字符串. 例如:下面两个都是定义字符串 str1 = "hello world" str2 = ...

  7. HM16.0帧内预测重要函数笔记

    Void TEncSearch::estIntraPredQT   亮度块的帧内预测入口函数 Void TComPrediction::initAdiPatternChType 获取参考样本点并滤波 ...

  8. matlab-罗曼诺夫斯基准则剔除粗大值

    罗曼诺夫斯基准则原理  罗曼诺夫斯基准则又称 t检验准则,其特点是首先删除一个可疑的的测得值,然后按 t分布检验被剔除的测量值是否含有粗大误差 罗曼诺夫斯基准则  1)选取合适的显著度a,选择合适的数 ...

  9. 12、K最近邻算法(KNN算法)

    一.如何创建推荐系统? 找到与用户相似的其他用户,然后把其他用户喜欢的东西推荐给用户.这就是K最近邻算法的分类作用. 二.抽取特征 推荐系统最重要的工作是:将用户的特征抽取出来并转化为度量的数字,然后 ...

  10. 剑指offer题目系列二

    本篇延续上一篇,介绍<剑指offer>第二版中的四个题目:从尾到头打印链表.用两个栈实现队列.旋转数组的最小数字.二进制中1的个数. 5.从尾到头打印链表 题目:输入一个链表的头结点,从尾 ...