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. 基于webSocket的聊天室

    前言 不知大家在平时的需求中有没有遇到需要实时处理信息的情况,如站内信,订阅,聊天之类的.在这之前我们通常想到的方法一般都是采用轮训的方式每隔一定的时间向服务器发送请求从而获得最新的数据,但这样会浪费 ...

  2. ECSHOP和SHOPEX快递单号查询德邦插件V8.6专版

    发布ECSHOP说明: ECSHOP快递物流单号查询插件特色 本ECSHOP快递物流单号跟踪插件提供国内外近2000家快递物流订单单号查询服务例如申通快递.顺丰快递.圆通快递.EMS快递.汇通快递.宅 ...

  3. php 电商系统SKU库存设计

    sku 全称为:Stock Keeping Unit,是库存进出计量的基本单元. 我们一般会在电商网站基本都会看到 比如淘宝,JD 淘宝和JD的 方式可能不一样,因为我不清楚他们具体是如何设计的, J ...

  4. 5.Python的语言特点

    前言   Python有哪些语言特点?可以列出的特点很多,例如,<Python核心编程>第二版列出了十多条特点.本文的三个特点是笔者学习Python的体会,其他特点有体会之后再写,笔者是这 ...

  5. 来自一个大三开学三周的huster的迷茫与失措

    大三开学考研保研的话题开始多了起来.自从前天去听了一回谢长生教授的实验室宣讲会,回来直到现在都好像心头上压了些东西,喘不过气来.本来我就少与外界接触,加之我自己一个人主动学习的积极性也很是缺乏,所以当 ...

  6. Qt——菜单栏、工具栏、状态栏

    1.菜单栏 菜单栏的意义是将可点击触发最终事件的集中在一起,所以菜单栏中是QAction 添加菜单栏是QMainWindow的行为 QMenubar *menubar = this->addMe ...

  7. 线程基础三 使用C#中的lock关键词

    C#中lock关键字主要是为确保当一个线程使用某些资源时,同时无法其他线程无法使用该资源.下面我们看看下面的小例子. static void Main(string[] args) { var c = ...

  8. SELECT(データ取得)

    WHERE 句は.満たすべき条件を指定することにより選択される行数を制限します. WHERE 句は.SELECT 命令と同様に OPEN CURSOR.UPDATE.および DELETE 命令でも使用 ...

  9. python,函数式编程

    函数式编程: 特点:允许传递的参数是函数,且允许返回一个函数. 由于Python允许使用变量,因此,Python不是纯函数式编程语言,同样的输入可能输出不同,有副作用.纯函数式编程语言没有变量,输入和 ...

  10. python基础之try异常处理、socket套接字基础part1

    异常处理 错误 程序里的错误一般分为两种: 1.语法错误,这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正 2.逻辑错误,人为造成的错误,如数据类型错误.调用方法错误等,这些解 ...