14.1 简介
14.1.1 基本概念
Generator 函数
是什么:可以从以下几个角度理解
- 状态机:
Generator
函数是一个状态机,封装了多个内部状态 - 遍历器对象生成函数:执行
Generator
函数会返回一个遍历器对象,通过返回的遍历器对象,可以依次遍历Generator
函数内部的每一个状态
定义:Generator
函数是一个普通函数,但有两个特征
function
关键字与函数名之间有一个*
- 函数体内部使用
yield
语句,定义不同的内部状态
调用:调用方法与普通函数一样,也是在函数名后面加上()
, 调用后返回一个便利器对象
(一个有着value
和done
两个属性的对象)
1 | function * foo(x, y) { ··· } |
返回的便利器对象的 next() 方法
说明:调用遍历器对象
的next
方法,使得指针移向下一个状态
参数 | 说明 | 必需 |
---|---|---|
1 | 该参数就会被当作上一个yield 语句的返回值 |
否 |
原理:Generator
函数是分段执行的,yield
语句是暂停执行的标记,而next
方法可以恢复执行
1 | function* helloWorldGenerator() { |
yield 语句
说明:是Genertor
函数返回的便利器对象
的暂停标志,每次调用next
函数,运行逻辑如下
遇到
yield
语句,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。下一次调用
next
方法时,再继续往下执行,直到遇到下一个yield
语句。如果没有再遇到新的
yield
语句,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。如果该函数没有
return
语句,则返回的对象的value
属性值为undefined
。
语法:
yield
语句如果用在一个表达式之中,必须放在()
里面(用作函数参数或赋值表达式的右边,可以不加()
)- 通过
yeild
调用Genertor
函数需要在中间加上*
限制:yield
语句不能用在普通函数中,否则会报错
注意:yield
语句后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行
技巧:Generator
函数可以不用yield
语句,这时就变成了一个单纯的暂缓执行函数
1 | var arr = [1, [[2, 3], 4], [5, 6]]; |
yeild 和 return
说明:在Genertor
函数中可以同时使用yeild
和return
相同点:都能返回紧跟在语句后面的那个表达式的值
不同点:
- 一个函数里面,只能执行一次(或者说一个)
return
语句,但是可以执行多次(或者说多个)yield
语句 - 每次遇到
yield
,函数暂停执行,下一次再从该位置继续向后执行,而return
语句不具备位置记忆的功能(遇到return
,便利器对象的遍历就到头了)
与Iterator
接口的关系
说明:可以把Generator
赋值给对象的Symbol.iterator
属性,从而使得该对象具有Iterator
接口
注意:调用Genertor
函数返回的便利器对象自身也有Symbol.iterator
属性,指向便利器对象自身
Demo1: Generator
函数赋值给Symbol.iterator
属性
1 | var myIterable = {}; |
Demo2
1 | function* gen(){ |
14.2 next方法的参数
参数 | 说明 | 必需 |
---|---|---|
1 | 该参数就会被当作上一个yield 语句的返回值 |
否 |
用途:可以在Generator
函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为
注意:由于next
方法的参数表示上一个yield
语句的返回值,所以第一次使用next
方法时,不能带有参数
Demo1: 演示 next 参数的运行逻辑
1 | function* foo(x) { |
Demo2: 想要第一次调用next
方法时,就能够输入值,可以在Generator
函数外面再包一层
1 | /** |
14.3 for…of循环
说明:不需要调用next
方法,for...of
循环可以自动遍历Generator
函数生成的Iterator
对象
注意:和直接调用next
方法不同,return
的值不会被遍历
扩展:除了for...of
循环以外,还有以下运算可以利用Generator
函数
- 扩展运算符
- 解构赋值
Array.from
方法
Demo1: return
的值不会被遍历
1 | function *foo() { |
Demo2: 斐波那契数列
1 | /** |
Demo3: 用 Genertor 函数包装对象属性的遍历(并没有为对象部署 Iterator
接口 )
1 | function* objectEntries(obj) { |
Demo4: 将Generator
函数加到对象的Symbol.iterator
属性上面(成功部署了 Iterator
接口)
1 | function* objectEntries() { |
14.4 Generator.prototype.throw()
说明:Generator
函数返回的遍历器对象
,都有这个实例方法,可以在函数体外抛出错误,然后在Generator
函数体内捕获
throw
方法被捕获以后,会附带执行下一条yield
语句(就是说,会附带执行一次next
方法)- 一旦
Generator
执行过程中抛出错误,且没有被内部捕获,JavaScript
引擎认为这个Generator
已经运行结束了。如果此后还调用next
方法,将返回一个value
属性等于undefined
、done
属性等于true
的对象
参数 | 类型 | 说明 | 必需 |
---|---|---|---|
1 | any |
该参数会被catch 语句接收,建议抛出Error 对象的实例 |
否 |
注意:不要混淆遍历器对象的throw方法
和全局的throw命令
- 后者只能被函数体外的
catch
语句捕获 throw
命令与Generator.prototype.throw()
是无关的,两者互不影响
1 | var gen = function* gen(){ |
14.5 Generator.prototype.return()
说明:可以返回给定的值,并且终结遍历Generator
函数
参数 | 说明 | 必需 |
---|---|---|
1 | 该参数将作为return() 方法的返回的对象中的value 属性值被返回 |
否,不提供参数,则返回值的value 属性为undefined |
注意:如果Generator
函数内部有try...finally
代码块,那么return
方法会推迟到finally
代码块执行完再执行。
1 | function* numbers () { |
14.6 yield*语句
说明:如果yield命令后面跟的是一个遍历器对象(包括Generator()
函数返回的)需要在yield
命令后面加上星号,表明它返回的是一个遍历器对象
- 运行结果就是使用一个遍历器,遍历了多个
Generator
函数,有递归的效果。 - 任何数据结构只要有
Iterator
接口,就可以被yield*
遍历
返回值:如果yield*
后面是对Generotor
函数的调用,则该Generator
函数的返回值就是整个yeild* 语句
的返回值
注意:在Generater
函数内部,按照普通方式调用另一个Generator
函数,默认情况下是没有效果的
Demo1: 基本使用
1 | function* foo() { |
Demo2: 取出嵌套数组的所有成员
1 | /** |
Demo3: 使用`yield`语句遍历完全二叉树*
1 | /** |
14.7 作为对象属性的Generator函数
完整形式
1 | let obj = { |
简写形式
1 | let obj = { |
14.8 Generator函数的this
说明:执行Generator
函数总是返回一个遍历器对象,这个便利器对象就Generator
函数的实例,但不同于通过new
创建的实例,Generator
函数中的this
不指向这个遍历器对象
注意:虽然返回的遍历器对象是 Generator
函数的实例,但和普通函数有以下不同点
Generator
函数也不能跟new
命令一起用- 虽然在
Generator
函数内部的this
指向返回的便利器对象,但无法通过this
向这个对象添加实例成员
Demo1: 返回的遍历器是Generator
函数的实例
1 | // Generator 函数 |
Demo2: 生成一个空对象,使用bind
方法绑定Generator
函数内部的this
缺点:绑定了 Generator
函数中的 this
的对象和返回的便利器对象不是同一个对象
1 | function* F() { |
Demo3: 封装 Generator 函数,优点是
1. 能使用 new
2. 使返回的遍历器对象能访问绑定在 this
上的实例成员
1 | function* gen() { |
14.9 状态机与协程
14.9.1 Generator 与 状态机
说明:Generator
是实现状态机的最佳结构
Demo: clock函数一共有两种状态(Tick和Tock),每运行一次,就改变一次状态
1 | var clock = function*() { |
14.9.2 Generator 与协程
说明:Generator
函数是对协程
的实现,但属于不完全实现。
用途:如果将Generator
函数当作协程,完全可以将多个需要互相协作的任务写成Generator
函数,它们之间使用yield
语句交换控制权。
14.10 应用
14.10.1 异步操作的同步化表达
说明:Generator
函数的一个重要实际意义就是用来处理异步操作,改写回调函数
Demo1: 所有Loading界面的逻辑,都被封装在一个函数,按部就班非常清晰
1 | function* loadUI() { |
Demo2: 通过Generator函数部署Ajax操作
1 | function* main() { |
Demo3: 通过Generator
函数逐行读取文本文件
1 | function* numbers() { |
14.10.2 控制流管理
Demo1: 使用 Generator 批量执行任务(只能用于所有步骤都是同步操作的情况,不能有异步操作的步骤)
1 | // 封装了一个任务的多个步骤 |
14.10.3 部署Iterator接口
说明:利用Generator
函数,可以在任意对象上部署Iterator
接口
1 | function* iterEntries(obj) { |
14.10.4 作为数据结构
说明:Generator
可以看作是数据结构,更确切地说,可以看作是一个数组结构
Demo: 像处理数组那样,处理这三个返回的函数
1 | function *doStuff() { |