[JavaScript]ECMA-6 yield语法
概述
yield关键字用于并且仅限于生成器函数(generator)内部,作用是暂停(并返回)/重启(可选修改该栈环境变量)该函数栈环境。
一般语法
调用生成器函数时返回一个可迭代对象,当调用该对象的next()方法时,函数会在遇到yield关键字的位置马上返回一个IteratorResult对象,该对象格式如下:
{value: [Mixed], done: [boolean]}
yield的行为类似于return,不同的地方在于,yield返回后并不等同于该次函数调用的结束,该函数的栈环境仍然存在于内存当中(暂停),等待下一次调用到来时,代码段将会在上一次yield返回的位置继续往下执行(重启),
这样就实现了函数的暂停/重启行为。
function* foo(index) { let a = 2; while (index < a) { c = yield index++; } } const iterator = foo(0); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next());
输出:
{value: 0, done: false}
{value: 1, done: false}
{value: undefined, done: true}
可见每次调用next()都会在yield处返回该变量的结果并不再往下执行,当下次调用next()时,该函数会在上一次yield的位置继续往下执行,直到函数真正执行完毕,返回对象的done属性被赋值为true。
改变函数内部变量
yield还有另外一种能力,就是改变该函数栈内变量的值。
[rv] = yield [expression];
调用next()时接受一个参数
generator.next([vaule]);
这样多次调用(除第一次)时,就会先把vaule赋值给函数内部变量rv再继续往下执行
示例:
function* foo(index) { let a = 2; var b; while (index < a) { b = yield b; } } const iterator = foo(0); console.log(iterator.next('a')); console.log(iterator.next('b')); console.log(iterator.next('c'));
输出:
{value: undefined, done: false}
{value: "b", done: false}
{value: "c", done: false}
当yield前加=号赋值给函数内部某个变量时,在第一次调用将不会给该变量赋予任何形式的值,所以在这里的第一行输出时,value等于undefined,并不是设想的被赋值为a。让next([vaule])真正起作用,是在多次调用的时候,在重启函数时,先把该函数栈内的rv变量赋值为value后再继续执行。这有点类似于Python的nex()和send(),只不过ECMA-6只用了next()来实现这种功能。
这里有一个难点,就是难以理解rv是何时改变它的值的,为了更清晰的说明这个问题,我们更改一下上面的例子,在yield前输出一下变量b
function* foo(index) { let a = 2; var b; while (index < a) { console.log(b); b = yield b; } } const iterator = foo(0); console.log(iterator.next('a')); console.log(iterator.next('b')); console.log(iterator.next('c'));
输出:
undefined {value: undefined, done: false} b {value: "b", done: false} c {value: "c", done: false}
可以看见,每次调用在yield <statement>执行完成后,不管接下来是任何代码,函数都会暂停,包括return yield <statement>。示例代码中第一次调用:
console.log(b);//因为b确实还没有被赋值,输出undefined还是比较容易理解的
b = yield b;//yield b返回b的值给IteratorResult对象,这时b依然是没有任何赋值的,所以也是undefined。并且执行完yield b后函数马上暂停
直到第二次调用
iterator.next('b');
这时调用函数next()传入值"b","b"被赋值到变量b,代码继续往下执行,由于是一个循环语句,于是代码又来到了
console.log(b);//此时b已经被赋了值,所以输出"b"
b = yield b;//yield b也返回b的值给IteratorResult对象,这时b依然是"b",然后马上暂停,等待下一次调用
return yield
有一种情况,就是该类函数如果包含return语句又会怎样呢?我们来看看MDN对这个行为的描述:
- A
returnstatement is reached. In this case, execution of the generator ends and anIteratorResultis returned to the caller in which thevalueis the value specified by thereturnstatement anddoneistrue.
如预料之中的一样,return会终止函数,并返回return语句的结果,放置在IteratorResult.vaule上,同时这也意味着整个迭代结束,IteratorResult.done为true.
但是,如果是return yield [statement]呢?来看看下面的例子:
function* foo(index) { let a = 3; while (index < a) { return yield index++; } } const iterator = foo(0); console.log(iterator.next('a')); console.log(iterator.next('b1')); console.log(iterator.next('c'));
输出:
{value: 0, done: false}
{value: "b1", done: true}
{value: undefined, done: true}
如果结合上小段介绍,即“改变函数内部变量”,就不难理解这个例子的行为;
在第一次调用,next()返回未经递增的index,即0,并且暂停函数;
第二次调用,重启函数,把参数的值"b1"赋值给函数栈环境的变量,但是由于这里遇到的是return,所以"b1"自然而然的返回给了IteratorResult.vaule,并且结束整个迭代;
第三次调用,由于迭代已经结束,所以这里的调用已经没有任何意义;

浙公网安备 33010602011771号