js yield的一点理解

js generator是一个状态机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function* foo() {
a++;
yield;
b = b * a;
a = (yield b) + 3;
}

function* bar() {
b--;
yield;
a = (yield 8) + b;
b = a * (yield 2);
}

function step(gen) {
let it = gen();
let last;
return function () {
last = it.next(last).value;
}
}

let a = 1;
let

js generator是一个状态机

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
function* foo() {
a++;
yield;
b = b * a;
a = (yield b) + 3;
}

function* bar() {
b--;
yield;
a = (yield 8) + b;
b = a * (yield 2);
}

function step(gen) {
let it = gen();
let last;
return function () {
last = it.next(last).value;
}
}

let a = 1;
let b = 2;
let s1 = step(foo);
let s2 = step(bar);
s2();
s2();
s1();
s2();
s1();
s1();
s2();
console.log(a, b);

这段代码在最后一个s2的时候会出现一个问题,按照一般的想法,前一个s1()已经把a变为12了,最后这个s2执行

1
b = a * (yield 2);

应该是b=12*2
但最后输出的是12,18,并不是12,24。
//原因就在于生成器是一个状态机,yield是状态的分界线,next是切换到下一个状态。

1
b = a * (yield 2);

这段bar()中的代码左边是b=a*,
因为js默认顺序是ltr(left to right),并且生成器需要保持上下文状态,所以这句话在执行的时候
先来获取a的值,就是9,然后yield暂停并保持a的值的状态。然后再执行两个s1()虽然改变了a的值,但是由于s2()状态机保持了状态所以导致s2()中的a的值还是9。
//由于yield左边运算是上一个状态,也就是说上一个状态a=9,然后再是b=9*2,所以b=18

如果把代码换成

1
b = (yield 2) * a;

那就会输出12,24了
因为ltr,所以先通过yield暂停了,没有先获取a的值所以没有保持a的值,导致两个s1()执行完毕后执行s2()之后a要重新获取

//因为yield右边不是上一个状态,而是下一个状态,所以a从全局变量a获取就是12,然后再是2*12=24
//所以输出12,24

整个完整的运行步骤

1
2
3
4
5
6
7
8
9
10
s2的状态 a=1,b=1,barlast=undefined
s2的状态 a=1,b=1,barlast=8
s1的状态 a=2,b=1,foolast=undefined
s2的状态 a=9,b=1,barlast=2
s1的状态 a=9,b=9,foolast=9
s1的状态 a=12,b=9,foolast=done
s2的状态
//(注意这里s2() yield左边运算是上一个状态,也就是说上一个状态a=9,然后再是b=9*2)
所以此时
a=12,b=18,barlast=done