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. dom4j里面封装方法的操作

    animal.xml <?xml version="1.0" encoding="UTF-8"?><animal>   <cat ...

  2. 【mysql学习笔记整理】

    /*mysql学习笔记整理*/ /*常用的数据库操作对象*/ #库的操作#创建#数据库的创建USE mysql;CREATE DATABASE db_x;#删除#删除数据库DROP DATABASE ...

  3. you don't have permission to access forbidden

    前几天装一个phpStudy 集成环境,打开测试页面的时候突然出现如下错误: 有一些小总结. 一些小的开发测试在本地开发的话,直接localhost/file  就可以,  如果涉及到大的开发环境,一 ...

  4. EpiiAdmin 开源的php交互性管理后台框架, 让复杂的交互变得更简单!Phper快速搭建交互性平台的开发框架,基于Thinkphp5.1+Adminlte3.0+Require.js。

    EpiiAdmin EpiiAdmin php开源交互性管理后台框架,基于Thinkphp5.1+Adminlte3.0+Require.js, 让复杂的交互变得更简单!Phper快速搭建交互性平台的 ...

  5. python3 练习题100例 (二十三)与7相关的数

    与7相关的数:如果一个正整数,它能被7整除或者它的十进制表示法中某个位数上的数字为7,则称之为与7相关的数.(10分) 题目内容: 现在我们给定一个正整数n(n<1000),求所有小于等于n的与 ...

  6. ant + jmeter 自动化接口测试环境部署

    1.jdk下载安装 下载地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html 2.jmeter下载 jmeter官 ...

  7. latex03-LaTeX中的中文处理办法

    编译含中文的tex文件的前提有三个: 默认编译器为XeLaTeX: 编辑器的默认字体编码格式为UTF-8: 引入ctex的宏包. 效果: 源码: %导言区 \documentclass{article ...

  8. 人人都会设计模式:观察者模式--Observer

    https://segmentfault.com/a/1190000012295887 观察者模式是抽像通知者和观察者,达到具体通知者跟具体观察者没有偶合.能达到不管是切换通知者,或者是切换观察者,都 ...

  9. C语言实现计算二进制数字1的个数

    #include<stdio.h> #include<stdlib.h> int print_one_bits01(unsigned int value){ //0000 11 ...

  10. 剑指offer题目系列一

    本篇介绍<剑指offer>第二版中的四个题目:找出数组中重复的数字.二维数组中的查找.替换字符串中的空格.计算斐波那契数列第n项. 这些题目并非严格按照书中的顺序展示的,而是按自己学习的顺 ...