JavaScript中的Generator函数
Posted Tyler‘s Blog
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript中的Generator函数相关的知识,希望对你有一定的参考价值。
1. 简介
Generator函数时ES6提供的一种异步编程解决方案。Generator语法行为和普通函数完全不同,我们可以把Generator理解为一个包含了多个内部状态的状态机。
执行Generator函数回返回一个遍历器对象,也就是说Generator函数除了提供状态机,还可以生成遍历器对象。Generator可以此返回多个遍历器对象,通过这个对象可以访问到Generator函数内部的多个状态。
形式上Generator函数和普通的函数有两点不同,一是function关键字后面,函数名前面有一个星花符号“*”,二是,函数体内部使用yield定义(生产)不同的内部状态。
执行Generator函数返回的是一个遍历器对象,这个对象上有一个next方法,执行next方法会返回一个对象,这个对象上有两个属性,一个是value,是yield关键字后面的表达式的值,一个是done,布尔类型,true表示没有遇到return语句,可以继续往下执行,false表示遇到return语句。来看下面的语句:
function* helloWorldGenerator () { yield \'hello\'; yield \'world\'; return \'ending\'; } var hw = helloWorldGenerator(); console.log(hw.next()); //第一次调用,Generator函数开始执行,直到遇到yield表达式为止。next方法返回一个对象,它的value属性就是当前yield语句后面表达式的值hello,done属性为false,表示遍历还没有结束 console.log(hw.next()); //第二次调用,Generator函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。next方法返回的对象的value属性就是当前yield语句后面表达式的值world,done属性值为false,表示遍历还没有结束。 console.log(hw.next()); //第三次调用,Generator函数从上次yield表达式停下的地方,一直执行到return语句(如果没有return语句,则value属性为undefined),done属性为true,表示遍历已经执行结束。 console.log(hw.next()); //第四次调用,此时Generator函数已经执行完毕,next方法返回对戏那个的value属性为undefined,done属性为true,表示遍历结束。 console.log(hw.next()); //第五次执行和第四次执行的结果是一样的。
执行结果如下图:
1. 定义Generator函数helloWorldGenerator函数
2. 函数内部有2个yield表达式和一个return语句,return语句结束执行
3. Generator函数的调用方法和普通函数一样,也是在函数名后面加上一对圆括号。不同的是调用之后,函数不是立即执行,返回的也不是return语句的结果undefined,而是一个指向内部状态的指针对象,也就是上面说的遍历器对象(Iterator Object)
4. 调用遍历器对象的next方法,状态指针移动到下一个状态,返回{value: "hello", done: false}
5. 调用遍历器对象的next方法,状态指针移动到下一个状态,返回{value: "world", done: false}
6. 调用遍历器对象的next方法,状态指针移动到下一个状态,返回{value: "ending", done: true},done为true,说明已经遇到了return语句,后面已经没有状态可以返回了
7. 调用遍历器对象的next方法,指针不再移动,返回{value: undefined, done: true}
8. 调用遍历器对象的next方法,指针不再移动,返回{value: undefined, done: true}
注意yield表达式后面的表达式,只有当调用next方法,内部指针指向该语句时才会执行,相当于javascript提供了手动的“惰性求值”语法功能。
function* gen() { yield 123 + 456; }
上面代码中,yield后面表达式123 + 456,不会立即求值,只会在next方法将指针移动到这一句时,才会求值。
yield表达式语句和return语句有相似之处,也有却别。相似的地方是都能返回紧跟在语句后面的那个表达式的值。却别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,return语句没有位置记忆功能。一个函数里面,只能执行一次return语句,但是可以多次执行yield表达式。也就是说Generator可以逐次生成多个返回值,这也是它的名字的来历。
Generator函数中可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。看下面代码:
function* f () { console.log(\'执行了!\') } var generator = f(); setTimeout(function () { console.log(generator.next()); // 执行Generator函数,只到遇到yield表达式,这里没有就直接输出:"执行了!",函数返回{"done":true}没有value }, 2000);
输出结果如下:
Generator函数f()中没有yield表达式,但是仍然还是一个Generator函数。如果函数f是一个普通函数,在执行var generator = f();的时候就会输出“执行了!”。但是f()是一个Generator函数,就变成了只有调用next方法的时候,函数f才会执行。
另外需要注意,yield表达式只能用在Generator函数里面,用在其他地方都会报错。看下面的代码:
var arr = [1, [[2, 3], 4], [5, 6]]; var flat = function* (a) { a.forEach(function (item) { if (typeof item !== \'number\') { yield * flat(item) } else { yield item } }) } for (let f of flat) { console.log(f); }
上面代码会报错,因为forEach方法的参数是一个普通函数,但是在里面使用了yield表达式。可以把forEach改成for循环
var arr = [1, [[2, 3], 4], [5, 6]]; var flat = function* (a) { var length = a.length; for (var i = 0; i < length; i++) { var item = a[i]; if (typeof item !== \'number\') { yield *flat(item) } else { yield item; } } } for (var f of flat(arr)) { console.log(f); }
输出结果如下:
另外,如果yield表达式用在另外一个表达式之中,必须放在圆括号内部。如下:
function *demo() { console.log(\'hello \' + (yield)); console.log(\'world \' + (yield 123)); } var gen = demo(); console.log(gen.next()); console.log(gen.next()); console.log(gen.next());
输出结果如下:
1. 定义Generator函数demo
2. 函数内部有输出"hello"+(yield)和“world”+(yield 123)
3. 调用demo方法得到遍历器对象gen
4. 调用遍历器对象的next方法并输出,注意先执行表达式语句“hello” + (yield),得到{value: undefined, done: false},再输出:“hello undefined”。注意直接输出yield表达式得到的结果是undefined,必须使用遍历器对象的next方法才能获取yield表达式后面的值
5. 调用遍历器对象的next方法并输出,注意先执行表达式语句“worold” + (yield),得到{value: 123, done: false},再输出:“world undefined”。注意直接输出yield表达式得到的结果是undefined,必须使用遍历器对象的next方法才能获取yield表达式后面的值
6. 调用遍历器对象的next方法,因为后面已经没有yield表达式,虽然没有return语句,判断依据是否有更多的yield语句为标准,还是输出{value: undefined, done: true}。done的值是true。后面无论调用next方法多少次,都是这个结果。
yield表达式用作函数或者放在赋值表达式的右边,可以不加括号。如下:
function* demo() { foo(yield \'a\', yield \'b\'); // OK let input = yield; // OK }
上面说到yield表达式本身输出的是undefined,也就是说yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数会被当做上一个yield表达式的返回值。
function *f() { for(var i=0; true; i++){ var reset = yield i; if(reset) { i = -1 } } } var g = f(); console.log(g.next()); console.log(g.next()); console.log(g.next(true));
返回结果如下:
上面代码返回一个可以无限运行的Generator函数f,如果next方法没有参数,每次运行到yield表达式,变量reset的值总是yield表达式的值undefined。当next方法带一个参数true时,变量reset就被重置为这个参数的值,即true,因此i的值会等于-1,下一轮循环就会从-1开始递增。这个功能有很重要的语法意义。Generator函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next方法的参数,就有办法在Generator函数开始运行之后,继续向函数体内部注入值。也就是说,在Generator函数运行的不同阶段,从外部向内部注入不同的值,可以调整函数行为。
看下面的例子:
function* foo (x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var a = foo(5); console.log(a.next()); console.log(a.next()); console.log(a.next()); var b = foo(5); console.log(b.next()); console.log(b.next(12)); console.log(b.next(13));
运行结果如下图:
1. 申明一个Generator函数foo
2. 调用函数foo,传入参数5,得到遍历器对象a
3. 调用遍历器对象a的next方法,返回yield关键字后面表达式(x + 1)的值,得到6,返回结果{ value: 6, done: false }。
4. 调用遍历器对象a的next方法,往下执行,因为执行next的时候没带参数,上一次yield表达式的值从6变成undefined,而不是6,y的值是2 * undefined,即为NaN。本次yield表达式的值是undefined / 3 为NaN。最后返回结果{ value: undefined, done: false }
5. 调用遍历器对象a的next方法,往下执行,因为执行next的时候没有带参数,上一次yield表达式的值为从NaN变成undefined,因此z的值是undefined,返回的值为5 + NaN + undefined,即为NaN
6. 调用函数foo,传入参数5,得到遍历器对象b
7. 调用遍历器对象的next方法,返回yield关键字后面表达式(x + 1)的值,得到6,返回结果{ value: 6, done: false }
8. 调用遍历器对象的next方法,传参12,因此上一次yield关键字后面的表达式的(x + 1)的值从6变为12,y的值是2 * 12,即为24。yield关键字后面表达式的值为 (24 / 3),即为8。最后返回结果{ value: 8, done: false }
9. 调用遍历器对象的next方法,传入参数13,因此上一次yield关键字后面的表达式(y / 3)的值从8变成13,z的值是13。表达式(x + y + z)的值是(5 + 24 + 13),即42。最后返回结果{ value: 24, done: true }
注意,由于next方法的参数表示上一个yield表达式的返回值,所以在第一调用next方法时,传递参数是无效的。JavaScript引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上说,第一个next方法用来启动遍历器对象,所以不用带参数。
再看一个例子:
function * dataConsumer () { console.log(\'started\') console.log(`1.${yield }`) console.log(`2.${yield }`) return \'result\' } let genObj = dataConsumer() genObj.next() genObj.next(\'a\') genObj.next(\'b\')
输出结果:
1. 定义Generator函数dataConsumer
2. 调用dataConsumer函数,得到遍历器对象genObj
3. 调用遍历器对象genObj的next方法,执行执行dataConsumer函数,只到遇到yield表达式为止。注意第一句输出“started”,第二句里就有yield表达式,因此在这里停止。最终结果是“started”
4. 调用遍历器对象genObj的next方法,传入参数‘a’,继续往下执行,上一次yield表达式的值从undefined变成‘a’,最后输出1.a
5. 调用遍历器对象genObj的next方法,传入参数‘b’,继续往下执行,上一次yield表达式的值从undefined变成‘b’,最后输出2.b
上面代码是一个很直观的例子,每次通过next方法向Generator函数注入值,然后打印出来。
如果想要第一次调用next方法时就能够输入值,可以在Generator函数外面再包一层。
function wrapper (generatorFunction) { return function (...args) { let generatorObject = generatorFunction(...args) generatorObject.next() return generatorObject } } const wrapped = wrapper(function *() { console.log(`first input: ${yield }`) return \'DONE\' }) wrapped().next(\'hello\')
输出结果:
上面代码中,Generator函数如果不用wrapper先包一层,是无法在第一次调用next方法的时候就输入参数的。 这个其实在包装函数wrapper里面已经先执行了一次next方法了。
2. Generator和Iterator接口的关系
任意一对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。
由于Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的Symbol.iterator属性,从而使这个对象具有Iterator接口。
var myIterable = {}; myIterable[Symbol.iterator] = function *() { yield 1; yield 2; yield 3; }; console.log([...myIterable]);
输出结果如下:
上面代码中Generator函数赋值给Symbol.iterator属性,从而使myIterator对象具有了iterator接口,这样就可以被...运算符遍历了。
Generator函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。
function *gen() { } var g = gen(); console.log(g[Symbol.iterator]() === g); // 输出true
上面代码中,gen是一个Generator函数,调用它会生成一个遍历器对象g,它的Symbol.iterator属性也是一个遍历器对象生成函数,执行后返回它自己。
for...of循环可以自动遍历Generator函数生成的Generator对象,并且不需要调用next方法,看下面的代码:
function* foo () { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v); }
输出结果如下:
上面代码中使用for...of循环,以此显示5个yield表达式的值。这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会终止,且不包含该返回对象,所以上面代码中return语句返回值6,不包括在for...of循环中。
下面是一个利用Generator函数和for...of循环,实现斐波那契数列的例子
function* fibonacci () { let [prev, curr] = [0, 1]; for (; ;) { [prev, curr] = [curr, prev + curr]; yield curr; } } for (let n of fibonacci()) { if (n > 1000) break; console.log(n); }
输出结果如下:
从上面代码可以看出,使用for...of语句时,不再需要使用next方法。注意这里用for...of循环代替了next方法,照样可以执行Generator函数。
利用for...of循环,可以写出遍历任意对象(Object)的方法。原生的JavaScript兑现更没有遍历接口,无法使用for...of循环,通过Generator函数为它加上这个接口,就可以使用了。
function* objectEntries (obj) { let propKeys = Reflect.ownKeys(obj); for (let propKey of propKeys) { yield [propKey, obj[propKey]]; } } let jane = {first: \'Jane\', last: \'Doe\'}; for (let [key, value] of objectEntries(jane)) { console.log(`${key}: ${value}`); }
输出结果如下:
上面代码中,原生对象jane不具备Iterator接口,无法用for...of遍历。这是我们通过Generator函数objectEntries为它加上遍历器接口,就可以用for...of遍历了。加上遍历器接口的另一种写法是,将Generator函数加到对象的Symbol.iterator属性上,代码如下:
function* objectEntries () { let propKeys = Object.keys(this); for (let propKey of propKeys) { yield [propKey, this[propKey]]; } } let jane = {first: \'Jane\', last: \'Doe\'}; jane[Symbol.iterator] = objectEntries; for (let [key, value] of jane) { console.log(`${key}: ${value}`); }
输出结果如下:
除了for...of循环,扩展运算符(...),解构赋值和Array.from方法内部调用的都是遍历器接口,这就是说,它们都可以将Generator函数返回的Iterator对象作为参数。看下面的代码:
function* numbers () { yield 1 yield 2 return 3 yield 4 } // 扩展运算符 console.log(...numbers()) // Array.from方法 console.log(Array.from(numbers())) // 解构赋值 let [x, y] = numbers() console.log(x, y) // for ... of循环 for (let n of numbers()) { console.log(n) }
输出结果如下:
3. Generator.property上的方法
3.1. Generator.property.throw()
Generator原型对象上有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。
var g = function* () { try { yield; } catch (e) { console.log(\'内部捕获\', e) } } var i = g(); i.next(); try { i.throw(\'a\'); i.throw(\'b\'); } catch (e) { console.log(\'外部捕获\', e); }
输出结果如下:
上面代码中,遍历器对象i连续抛出两个错误。第一个错误被Generator函数体内部的catch语句捕获。i第二次抛出错误,由于Generator函数内部的catch语句已经执行过了,不会再捕捉到这个错误,所以这个错误就被抛出了Generator函数体,被函数体外的catch语句捕获。
throw方法可以接受一个参数,参数会被catch语句接收,建议抛出Error对象实例。
var g = function* () { try { yield; } catch (e) { console.log(e) } } var i = g(); i.next(); i.throw(new Error(\'出错了!\'))
输出结果如下:
不要混淆遍历器对象的throw方法和全局的throw命令。上面代码的错误,是用遍历器对象的throw方法抛出的,而不是用throw命令抛出的。后者只能被函数体外的catch语句捕获。
var g = function* () { while (true) { try { yield; } catch (e) { if (e !== \'a\') { throw e; } console.log(\'内部捕获\', e) } } } var i = g(); i.next(); try { throw new Error(\'a\'); throw new Error(\'b\'); } catch (e) { console.log(\'外部捕获\', e); }
输出结果如下:
上面代码只捕获了a,是因为函数体外的catch语句块,捕获了抛出的a错误以后,就不会再继续try代码块里剩余的语句了。因为没有执行i.catch()语句,内部的异常不会被捕获。
如果Generator函数内部没有try...catch代码块,那么throw方法抛出的错误将被外部try...catch代码块捕获。
var gen = function* gen () { yield console.log(\'hello\'); yield console.log(\'world\'); } var g = gen(); g.next(); g.throw();
输出如下:
上面代码中给,g.throw抛出错误后,没有任何try...catch代码可以捕获这个错误,导致程序报错,终端执行。
throw方法抛出的错误要被内部捕获,前提是必须至少执行一次next方法。
function * gen () { try { yield 1 } catch (e) { console.log(\'内部捕获\') } } var g = gen() t.throw(1)
输出结果如下:
上面代码中,g.throw(1)执行时,next方法一次都没有执行过。这时,抛出的错误不会被内部捕获,而是直接在外部抛出,导致程序出错。这种行为其实很好理解,因为第一次执行next方法,等同于启动执行Generator函数的内部代码,否则Generator函数还没有开始执行,这时throw方法抛出错误只能抛出在函数外部。
throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法。
var gen = function* gen () { try { yield console.log(\'a\'); } catch (e) { } yield console.log(\'b\'); yield console.log(\'c\'); } var g = gen(); g.next(); g.throw(); g.next();
输出结果如下:
上面代码中,g.throw方法被捕获以后,自动执行了一次next方法。所以会打印b。另外,也可以看到,只要Generator函数内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历。
另外,throw命令和g.throw方法是无关的,两者互不影响。
var gen = function* gen() { yield console.log(\'hello\'); yield console.log(\'world\'); } var g = gen(); g.next(); try { throw new Error(); } catch (e) { g.next(); }
输出结果如下:
上面代码中,throw命令抛出的错误不会影响到遍历器的状态,所以两次执行next方法,都进行了正确的操作。
这种函数体内捕获错误的机制,方便了对错误的处理。多个yield表达式可以只用一个try...catch代码块来捕获错误。如果使用回调函数的写法,想要捕获多个错误,就不得不为每个函数内部写一个错误处理语句,现在只在Generator函数内部洗写一次catch语句就可以了。
Generator函数体外抛出的错误,可以在函数体内捕获;反过来,Generator函数体内抛出的错误,也可被函数体外的catch捕获。
function* foo () { var x = yield 3; var y = x.toUpperCase(); yield y; } var it = foo(); it.next(); try { it.next(42); } catch (err) { console.log(err); }
上面代码中,第二个next方法向函数体内传入一个参数42,,数值是没有toUpperCase方法的,所以会抛出一个TypeError错误,被函数体外的catch捕获。
一旦Generator执行过程中抛出错误,且没有被内部捕获,就不会再往下执行下去了。如果还调用next方法,将返回一个value属性为undefined,done属性为tru的对象,即JavaScript引擎认为这个Generator已经运行结束了。
function *g() { yield 1; console.log(\'throwing an exception\'); throw new Error(\'generator broke!\'); yield 2; yield 3; } function log(generator) { var v; console.log(\'starting generator\'); try{ v = generator.next(); console.log(\'第一次运行next方法\', v); } catch(err) { console.log(\'捕捉错误\', v); } try{ v = generator.next(); console.log(\'第二次运行next方法\',v); }catch(err){ console.log(\'捕捉错误\', v); } try{ v = generator.next(); console.log(\'第三次运行next方法\', v); } catch(err) { console.log(\'捕捉错误\', v); } console.log(\'caller done\'); } log(g());
执行结果如下:
上面代码一共三次运行next方法,第二次运行的时候会抛出错误,然后第三次运行的时候,Generator函数就已经结束了,不再执行下去。
3.2.Generator.property.return()
Generator函数返回的遍历器对象,还有一个return方法,可以返回给定值,并且终结遍历Generator函数。
function *gen() { yield 1; yield 2; yield 3; } var g = gen(); console.log(g.next()); console.log(g.return(\'foo\')); console.log(g.next());
执行结果如下:
上面代码中,遍历器对象g调用return方法之后,返回值的value属性就是return方法的参数“foo”。并且,Generator函数的遍历就终止了,返回值的done属性为true,以后再调用next方法,done属性的返回值总是true。
如果return方法调用时,不提供参数,则返回值的value属性为undefined。
function *gen() { yield 1; yield 2; yield 3; } var g = gen(); console.log(g.next()); console.log(g.return());
执行结果如下:
如果Generator函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完后再执行。
function * numbers() { yield 1; try{ yield 2; yield 3; } finally { yield 4; yield 5; } yield 6; } var g = numbers(); console.log(g.next()); console.log(g.next()); console.log(g.return(7)); console.log(g.next()); console.log(g.next());
执行结果如下:
上面代码中,调用return方法后,就开始执行finally代码块,然后等到finally代码块执行完,再执行return方法。 遇到return语句对象指针就会跳转到finally里去执行,只到把finally里的语句执行完再执行return语句。
next(),throw(),return()方法的共同点
next(),throw(),return()这三个方法本质上是同一件事情,可以放在一起理解。他们的作用个都是让Generator函数恢复执行,并且使用不同的语句替换yield表达式。
next()是将yield表达式替换成一个值。
const g = function* (x, y) { let result = yield x + y return result } const gen = g(1, 2) console.log(gen.next()) console.log(gen.next(1))
输出结果如下:
上面代码中,第二个next(1)方法相当于将yield表达式x + y替换成一个值1。如果next方法没有参数,就相当于替换成undefined。所以第二次调用next方法的时候如果不传参数,返回的结果是{ value: undefined, done: false }。
throw是将yield表达式替换成一个throw语句。
const g = function* (x, y) { let result = yield x + y return result } const gen = g(1, 2) console.log(gen.next()) gen.throw(new Error(\'出错了\'))
输出结果如下:
上面代码相当于将let result = yield x + y替换成let result = throw(new Error(\'出错了\'))
return语句时将yield表达式替换成一个return语句
const g = function* (x, y) { let result = yield x + y return result } const gen = g(1, 2) console.log(gen.next()) console.log(gen.return(2))
输出结果如下:
return语句相当于将let result = yield x + y替换成let result = return 2
4. yield*表达式
如果在Generator函数内部,调用跟另外一个Generator函数,默认情况下是没有效果的。看下面代码:
function* foo () { yield \'a\'; yield \'b\'; } function* bar () { yield \'x\'; foo(); yield \'y\'; } for (let v of bar()) { console.log(v); }
输出结果如下
上面代码中,foo和bar都是Generator函数,在bar函数中调用foo,是不会有任何效果的。可以使用yield*表达式来调用另外一个Generator函数。如下代码:
function* foo () { yield \'a\'; yield \'b\'; } function* bar () { yield \'x\'; yield *foo(); yield \'y\'; } for (let v of bar()) { console.log(v); }
执行效果如下:
function* bar() { yield \'x\'; yield* foo(); yield \'y\'; } // 等同于 function* bar() { yield \'x\'; yield \'a\'; yield \'b\'; yield \'y\'; } // 等同于 function* bar() { yield \'x\'; for (let v of foo()) { yield v; } yield \'y\'; } for (let v of bar()){ console.log(v); }
输出结果是相同的,在一个Generator函数中使用yield*调用另外一个Generator函数,相当于把另一个Generator函数中的yield表达式放在这个函数中执行。
function* inner () { yield \'hello!\'; } function* outter1 () { yield \'open\'; yield inner(); yield \'close\'; } var gen = outter1(); console.log(gen.next().value); console.log(gen.next().value); console.log(gen.next().value); function* outter2 () { yield \'open\'; yield* inner(); yield \'close\'; } var gen2 = outter2(); console.log(gen2.next().value); console.log(gen2.next().value); console.log(gen2.next().value);
输出结果如下:
上面代码中,outer2使用了yield*表达式,outer1没有使用。结果就是,outer1返回一个遍历器对象,outer2返回该遍历器对象的内部值。从语法角度看,如果yield表达式后面跟的是一个遍历器对象,需要在yield关键字后面加上星号,表明它返回的是一个遍历器对象,这被称为yield*表达式。
let delegatedIterator = (function* () { yield \'Hello!\'; yield \'Bye!\'; }()); let delegatingIterator = (function* () { yield \'Greetings!\'; yield* delegatedIterator; yield \'Ok, bye.\'; }()); for (let value of delegatingIterator) { console.log(value); }
执行结果如下:
上面代码中,delegatingIterator是代理者,delegatedIterator是被代理者,由于yield* delegatedIterator语句得到的值,是一个遍历器,所以要用星号表示。运行结果是用一个遍历器遍历了多个Generator,有递归的效果。
yield*后面的Generator函数没有return语句的时候,等同于在Generator函数内部,部署了一个for...of循环。如下代码:
function *concat(iter1, iter2) { yield * iter1; yield * iter2; } // 等同于 function * concat(iter1, iter2) { for(var value of iter1){ yield value; } for(var value of iter2){ yield value; } }
上面代码说明,yield*后面的Generator函数(没有return语句时),不过是for...of的一种简写形式,完全可以用后者代替前者。反之,在有return语句的时候需要使用var value = yield* iterator的形式获取return语句的值。
如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。如下代码:
function* gen () { yield* [\'a\', \'b\', \'c\'] } let g = gen() console.log(g.next()) console.log(g.next()) console.log(g.next()) console.log(g.next())
执行结果如下:
上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象。
实际上,任何数据结构,只要有Iterator接口,就可以被yield*表达式遍历。
let read = (function* () { yield \'hello\'; yield* \'world\'; })(); console.log(read.next().value); console.log(read.next().value);
返回结果如下:
上面代码中,yield表达式返回的是整个字符串,但是yield*表达式返回的是单个字符。因为字符串有Iterator接口,所以被yield*表达式遍历。
如果被代理的Generator函数有return语句,那么就可以向代理它的Generator函数返回数据。
function* foo () { yield 2; yield 3; return "foo"; yield 4; } function* bar () { yield 1; var v = yield* foo(); console.log("v: " + v); yield 5; } var it = bar(); console.log(it.next()); // {value: 1, done: false} console.log(it.next()); // {value: 2, done: false} console.log(it.next()); // {value: 3, done: false} console.log(it.next()); // "v: foo" {value: 5, done: true} console.log(it.next()); // {value: undefined, done: true}
执行结果如下:
1. 定义Generator函数foo
2. 定义Generator函数bar,在函数内部使用yield*表达式调用函数foo
3. 调用bar方法,得到遍历器对象it
4. 调用遍历器对象it的next方法,返回{ value: 1, done: false }
5. 调用遍历器对象it的next方法,返回Generator函数foo的第一个yield表达式返回的对象{ value: 2, done: false }
6. 调用遍历器对象it的next方法,返回Generator函数foo的第二个yield表达式返回的对象{ value: 3, done: false }
7. 调用遍历器对象it的next方法,foo结束,foo方法里面有return语句,返回值是“foo”,继续往下执行只到遇到yield语句,输出“v:foo” 并输出{ value: 5, done: false }
8. 调用遍历器对象it的next方法,Generator函数里已经没有yield语句,输出{ value: undefiined, done: true }
再看下面的例子
function* genFuncWithReturn () { yield \'a\'; yield \'b\'; return \'The result\'; } function* logReturned (genObj) {以上是关于JavaScript中的Generator函数的主要内容,如果未能解决你的问题,请参考以下文章
javascript异步编程之generator(生成器函数)与asnyc/await语法糖