Generator函数
基本概念
- Generator函数特征:
- function关键字和函数名之间有个星号(*),该星号并未规定特定位置,一般认为紧跟
function*
- 函数体内部使用yield表达式,定义不同的内部状态
- function关键字和函数名之间有个星号(*),该星号并未规定特定位置,一般认为紧跟
- Generator函数的执行:
- 调用Generator函数会返回一个指向该函数内部指针的对象(即遍历器对象:可进行循环遍历),该函数并不执行
- 之后使用next()方法来获取下一个yield表达式的值的对象,当最终结果为
{value: undefined, done: true}
时,表示遍历结束,再次调用,将返回同样的结果
- yield表达式:🍅
- 遍历器对象,遇到yield之后,就暂停后面的操作,并将yield表达式的值,赋给返回对象的value属性;若yield后面为空,则value属性为undefined
- 当调用next方法一直未遇到yield表达式,则运行到函数结尾,并将return的值作为value属性值;无return语句,则value属性值为undefined
- yield表达式后面的内容,只有当调用next方法时才会被执行,相当于提供了手动惰性求值的方法
- 当generator函数内无yield语句时,相当于单纯的暂停函数,调用next后执行该函数
- yield表达式只能出现在generator函数中,出现在其他地方会报错
- yield用在其他表达式内部时,需要使用小括号括起来;当yield用在函数参数/赋值表达式右侧时,可以不加括号
- generator与iterator的联系:🧃
- 任何对象的
[Symbol.iterator]
方法,等于该对象的遍历器生成函数,调用该方法会返回一个遍历器对象;所以可以将generator函数赋给对象的该方法,之后该对象就可以被循环遍历了,比如for,...
运算符等 - generator函数执行后,返回一个遍历器对象,该遍历器对象也有
[Symbol.iterator]
方法,执行后返回该对象本身
- 任何对象的
- generator函数的堆栈上下文:
- 一般的JavaScript代码运行时,会形成一个上下文环境的堆栈(先进后出结构),最后产生的上下文,最先退出堆栈
- generator产生的上下文环境,遇到yield后,会退出堆栈,但不消失(处于暂停状态),当调用next方法时,该上下文环境又会重新加入堆栈,冻结的变量和对象恢复执行
javascript
// 🍅:yield表达式
// 若yield表达式出现在赋值表达式右侧,赋值左侧的变量值为undefined
function* ge () {
return (yield 9)
}
// 结果:
const g = ge() // ge {<suspended>}
g.next() // {value: 9, done: false}
g.next() // {value: undefined, done: true}
// 🧃:让对象可遍历
const myObj = {}
myObj[Symbol.iterator] = function* () {
yield Date.now()
yield Date.now()
yield Date.now()
}
[...myObj] // [value1, value2, value3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
next方法
- yield表达式本身无返回值,或者说返回undefined
- next方法可以带一个参数,该参数会当作上一个yield表达式的返回值:🧂
- 可以通过这个特性,给generator函数注入不同的值,然后改变函数的行为🍕
- 由于next方法的参数是作为上一个yield表达式的返回值,所以在第一次使用next方法时传参是无效的,V8引擎会忽略第一次next的参数;第一个next方法也可以说是来启用遍历器对象的(只有第一次调用next方法,才会执行generator内部的代码)
- 若想在第一次next时就将参数传入内部,需要再次嵌套一个函数🥝【当前未懂】
javascript
// 🧂:yield表达式:
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
// 此时:得到:foo {<suspended>}
const f = foo(5)
// 此时:
// 1. 执行:yield (x + 1),返回:{ value: 6, done: false }
f.next()
// 此时:
// 1. 执行:y = 2 * (yield (x + 1)), 由于next无参,故:y = 2 * undefined = NaN
// 2. 执行:yield (y / 3), 由于y = NaN,返回:{ value: NaN, done: false }
f.next()
// 此时:
// 1. 执行:z = yield(y / 3),由于next无参,故:z = undefined
// 2. 执行:return (x + y + z),即:return (5 + NaN + undefined), 返回:{ value: NaN, done: true }
f.next()
// 🍕:状态的暂停/恢复:无限运行的generator函数
function* status () {
for (let i = 0; true; i++) {
const reset = yield i
if (reset) {
i = -1
}
}
}
const s = status()
s.next() // { value: 0, done: false }
s.next() // { value: 1, done: false }
s.next() // { value: 2, done: false }
s.next(3) // { value: 0: done: false }
// 🥝:第一次就将参数传入生成函数中【未懂】
function wrapper(generatorFunction) {
return function (...args) {
let generatorObject = generatorFunction(...args);
// 此处应该是先执行一次next,然后再执行就相当于将参数传入了
generatorObject.next();
return generatorObject;
};
}
const wrapped = wrapper(function* () {
console.log(`First input: ${yield}`);
return 'DONE';
});
wrapped().next('hello!')
// First input: hello!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
for..of循环
- for..of循环可以自动遍历Generator函数运行时生成的iterator对象,并且不再需要调用next方法:
- 一旦next方法返回的对象done属性为true,该循环会被终止,且不包含该返回对象;即for..of循环只会遍历yield语句,不会遍历return
- 利用for..of,结合generator,可以遍历任何对象的方法:🍎
- for..of、扩展运算符(...)、解构赋值、Array.from调用的,都是遍历器接口,都可以将generator返回的对象作为参数
javascript
// 🍎:遍历对象:for..of + generator
function* objectEntries(obj) {
const propKeys = Reflect.ownKeys(obj)
for (let propKey of propKeys) {
yield [propKey, obj[propKey]]
}
}
const name = { firstName: 'jade', lastName: 'chou' }
// 第一种:遍历对象的键: [['firstName', 'jade'], ['lastName', 'chou']]
[...objectEntries(name)]
// 第二种:[['firstName', 'jade'], ['lastName', 'chou']]
name[Symbol.iterator] = objectEntries
[...name]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Generator.prototype.throw()
- generator返回的遍历器对象,都有一个throw方法:即在函数体外使用该方法抛出错误,然后在generator函数体内捕获错误(前提函数体内必须有try-catch,否则被外部的catch捕获/抛出异常)
- throw方法接受一个参数(建议使用Error实例),该参数会被generator中的catch语句接收
- throw方法抛出的错误要被内部的catch捕获,前提是必须执行一次next方法(第一次的next相当于启动执行generator内部的代码)
- throw方法被内部catch捕获之后,会自动执行下一条yield语句(即自动执行一次next方法)
- 当generator函数体内抛出错误时,若函数体内有catch,将被捕获,否则被外部的catch捕获,否则中断执行,抛出异常
- 当generator函数体内抛出错误,且内部无catch语句,则该生成器函数不再执行;当再次调用next方法时,返回
{value: undefined, done: true}
Generator.prototype.return()
- 定义:返回给定的值(参数),并且终止遍历Generator函数:
{ value: 参数, done: true }
,下次再次调用时,总是返回:{ value: undefined, done: true }
- 若Generator函数内部有try-catch-finally语句,且正在执行try块,则return方法会直接进入finally块执行,返回的值是finally块中第一个yield,执行完所有的内容后,才返回
{ value: 参数, done: true }
next()、throw()、return()的异同点
- 相同点:都是恢复执行generator函数
- 不同点:
- next()是把yield语句替换成一个函数参数/undefined
- throw()是把yield语句替换成throw语句,然后顺便执行下一条yield
- return()是把yield语句替换成return语句,然后结束代码块的操作,有finally块则进入finally块中
yield*表达式
- 定义:在generator函数中执行另一个generator函数(自动遍历),就不需要自己在generator手动遍历generator函数了🍅
- 从语法角度说,若yield后面跟的是一个遍历器对象,则需在yield后面加上个*号,表明他返回的是遍历器对象
- 具备iterator接口的数据结构,都可以被yield*遍历,比如:String、Array、Map、Set、arguments、NodeList
- yield*平铺数组🥥
- yield*遍历完全二叉树【未看】
javascript
// yield*语句
function exap1 () {
yield 1
yield 2
yield 3
return 4
}
function exap2 () {
yield 11
yield 12
yield* exap1()
// 若需获取exap1的返回值,则需写成:const value = yield* exap1()
yield 13
return 14
}
function exap3 () {
yield 11
yield 12
// 若不是yield*,则运行到此处:exap3.next().value为一个遍历器对象
yield exap1()
yield 13
return 14
}
// 等同于:
function exap2 () {
yield 11
yield 12
yield 1
yield 2
yield 3
// 无return
yield 13
return 14
}
// 等同于
function exap2 () {
yield 11
yield 12
for (let i of exap1()) {
yield i
}
yield 13
return 14
}
// 🥥:yield*平铺数组:
function* flatArray (arr) {
if (Array.isArray(arr)) {
for (let i = 0; i < arr.length; i++) {
yield* flatArray(arr[i])
}
} else {
yield arr
}
}
[...flatArray(arr)]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
作为对象属性的generator函数
javascript
// 若一个对象的属性为generator函数,则写成:
const obj = {
*generator () {
// ...
},
// 相当于:
generator: function* () {
// ...
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
generator函数的this
- generator函数总是返回一个遍历器(g),该遍历器是generator函数的实例(
g instaceof generator === true
),并且继承了generator函数原型对象上的方法(g.me() === generator.prototype.me()
) - 若把generator当成构造函数,并不会生效,因为generator返回的是遍历器对象,而非this对象🍳
- generator不能跟new一起使用,会报类型错误(不是一个构造器)
- 让generator能够返回this并且能够使用new🍚
javascript
// 🍳:
function* generator() {
this.a = 11
}
const g = generator()
g.next()
g.a // undefined,拿不到属性a的值
// 🍚:generator有this,并且和new使用
function* gen() {
this.a = 12
yield this.b = 13
yield this.c = 14
}
function generator () {
return gen.call(gen.prototype)
}
const g = new generator()
g.next()
g.next()
g.next()
console.log(g.a, g.b, g.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
generator实现状态机
- generator是实现状态机的最佳结构:本身包含了一个状态信息,即目前是否处于暂停状态
javascript
// es5状态机
const ticking = true
const clock = function () {
if (ticking) {
console.log('tick')
} else {
console.log('tock')
}
ticking = !ticking
}
// es6状态机:更简洁,更安全,状态不会被篡改,符合函数式编程思想
const clock = function* () {
while(true) {
console.log('tick')
yield;
console.log('tock')
yield;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20