Generator 函数的语法 (2.5)

  • es6提供的一种异步编程解决方案
  • 可以理解成为一种状态机,它封装了多个内部状态,返回一个可遍历的对象,可以依次遍历Generator函数内部的状态

如何定义

  • function关键字后面加一个*
  • 内部使用yield关键字定义每个状态
function* generatorFunction(){
  yield 'hello';
  yield 'world';
  return 'ending'
}
  • 上面的Generator函数定义了三个状态

如何执行

let gf = generatorFunction();
  • 和普通函数一样,用()括号就能直接执行Generator函数,返回一个包含之前generatorFunction函数内部定义状态的遍历器对象
  • 通过.next()方法遍历返回的对象
gf.next()
// { done: false, value: "hello"}
gf.next()
// { done: false, value: "world"}
gf.next()
// { done: true, value: "ending"}
  • 遍历器对象通过next()方法遍历,返回一个带donevalue属性的对象,如果遍历完了,donetrue,否则为falsevalue表示当前状态的值

yield 表达式

  • Generator函数实际上提供了一种可暂停执行函数的方案,yield则作为函数的暂停点

遍历器对象的next()执行如下

  1. 遇到yield表达式暂停执行,并将yield后面的值作为返回对象的value值返回
  2. 下一次调用next()方法,函数继续往下执行,直到遇到下一个yield表达式
  3. 如果没有遇到yield表达式,则一直执行到return语句,把return后面的指作为对象的value返回
  4. 如果没有return则返回的valueundefined
  • yield只有在声明Generator函数内部出现,其他地方都会报错
  • yield表达式在另一个表达式中时,要加上括号

与 Iterator 关系

参照 《Iterator与for...of》,再来补充

next 方法的参数

  • yield表达式本身没有返回值,可以通过next()方法的参数来指定上一个yield表达式的返回值,这意味着可以在函数执行的过程中向函数里面注入外部的值,从而调整函数的运行方式

for...of 循环

  • for...of 循环可以直接遍历Generator函数生成的对象,不需要next函数
  • 一旦next方法返回对象的done属性为true,终止遍历

Generator.prototype.throw

  • Generator函数执行返回的遍历器对象都有一个throw方法,可以在函数体外抛出错误,在函数体内部捕获
function* g(){
  try{
    yield ;
  } catch (e){
    console.log('内部捕获' + e)
  }
}
var i = g();
g.next();
g.throw('1');
// 内部捕获 1 
  • Generator函数内部捕获之后,会自动执行一次next()方法

Generator.prototype.return

  • 直接调用Generator函数的return方法可以终止便利该函数,并且可以给定返回值
  • 如果Generator函数内部有try...finally块,那么return会推迟到finally块执行完之后再执行

next(), throw(), return() 的共同点

理解

三个方法都是恢复Generator函数的执行,只是把yield表达式替换成不同的语句

  • next()yield表达式换成一个值
  • throw()yield表达式换成一个 throw语句
  • return()yield表达式换成一个return语句

yield* 表达式

如果要在一个Generator函数里面执行另外一个Generator函数,需要在Generator函数里面用yield*表达式来定义另外一个Generator函数

  • yield后面的Generator函数,如果没有return语句,等同于在Generator函数内部部署一个for...of循环
  • 如果被代理的Generator函数有return语句,则可以向代理它的Generator函数返回数据

作为对象属性的 Generator 函数

如果一个对象的属性是Generator函数,可以写成下面的形式

let a = {
  * generatorMethod() {
    
  }
}

Generator 函数的 this 对象

  • Generator函数生成的遍历器对象也是Generator函数的实例,同时也继承了Generator函数的prototype对象
  • Generator函数内部使用this关键字添加属性,调用函数生成的遍历器对象不会继承该属性
  • 对于Generator函数不能使用new关键字

Generator 与状态机

可以利用更少的变量来实现一个状态机,因为它本身就是一个状态机,即目前是否处于暂停状态

Generator 与协程

协程是程序运行的一种方式,可以用单线程实现,也可以用多线程实现。前者可以看作特殊的“子例程”,后者可以看作特殊的线程

协程与子例程的差异

  • 传统的子例程subroutine采用 “先进后出” 的执行方式,协程与其不同,可以同时存在多个线程(或函数)并行执行,但只有一个线程(或函数)处于运行状态,其他的处于暂停态suspended, 多个线程(或函数)可以交换执行权,这种可以并行执行,可以交换执行权的线程(或函数),叫做协程

  • 从实现上看,子例程只占用一个栈,而协程可以占用多个栈,但只有一个栈是运行状态,通过牺牲内存的方式来达到任务并行的目的

协程与普通线程的差异

  • 协程与普通线程最大的差异是,协程多个任务只能有一个任务处于运行态,而线程允许多个任务同时处于运行态。这是有JavaScript是单线程语言所决定的。而且线程多个任务之间的运行状态由环境决定,谁先得到资源谁先执行,于是就出现了死锁的问题,协程的运行顺序由自己决定

  • 使用Generator函数实现协程,利用yield来交换控制权

Generator 与上下文

上下文:全局的上下文环境(context,可称为运行环境)

  • JavaScript在执行函数的时候,会产生一个函数运行时的上下文,这样就出现了上下文环境的堆栈,遵循后进先出的原则。

  • Generator函数不一样,当执行到yield关键字时候,函数的上下文环境的堆栈冻结当前状态,当调用next方法时,函数的上下文环境堆栈又重新加入调用栈,冻结的变量和对象恢复执行

应用

Generator 函数可以暂停执行,可以返回任意值,这使得其有多种应用场景

  • 异步操作的同步化表达
  • 控制流管理
  • 为任意对象部署Iterator接口
  • 作为数据结构
上次更新: 8/20/2018, 10:52:36 PM