Generator
概念
- Generator是ES6提供的一种异步编程解决方案
- 从语法上来看,Generator 函数是一个状态机,封装了多个内部状态
- 执行Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数.返回的遍历器对象,可以依次遍历Generator 函数内部的每一个状态
- 形式上,Generator 函数是一个普通函数,但是有两个特征:一是function关键字与函数名之间有一个
*
号,二是函数体内使用yield(产出)表达式定义不同的内部状态
调用方法
-
Generator 函数的调用方法和普通函数一样,也是在函数名后面加上一对圆括号.不同的是,Generator 函数调用后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器.下一步必须调用遍历器的next方法使得指针下移,使得指针移向下一个状态.也就是说每次调用next方法,函数指针就从函数头部或上一次停下来的地方开始执行,直到遇到yield表达式或return语句.换言之,Generator函数是分段执行的,yield是暂停执行的标记,而next方法可以恢复执行
function* helloWorldGenerator() { //这个Generator函数有三种状态,hello,world,ending yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator(); hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } 注意这里done也是true,和前面的不太一致 hw.next() // { value: undefined, done: true }
-
*
号也在哪个位置都可以.最好采用如下写法:function* foo(){}
yield表达式
- 由于Generator函数返回的遍历器对象只有调用next方法才会遍历下一个内部状态,所以提供了可以暂停执行的函数,yield表达式就是暂停标志
- yield表达式后面的表达式只有调用next方法,内部指针指向该语句才会执行,因此等于为JavaScript提供了手动的惰性求值.即yield表达式后面的值不会立即执行,只有next方法移到这一句时才会求值
- yield表达式与return语句的相似之处:都能返回紧跟在语句后面的表达式的值
- yield表达式与return语句的区别:遇到yield时,函数暂停执行,下一次从该位置开始继续向后执行.而return语句不具备位置记忆功能,一个函数里面只能执行一个return语句
- yield表达式只能用在Generator函数里,用在其他地方都会报错.即使是在Generator函数里嵌套的方法内使用也会报错
//下面这个函数虽然使用了Generator函数,但在forEach中使用yield,因此forEach中的参数是普通函数,因此会报错 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 (var f of flat(arr)){ console.log(f); }
- yield表达式如果用在另一个表达式中必须放在圆括号里面
function* demo() { console.log('Hello' + yield); // SyntaxError console.log('Hello' + yield 123); // SyntaxError console.log('Hello' + (yield)); // OK console.log('Hello' + (yield 123)); // OK }
-yield表达式用作函数参数或放在赋值表达式的右边可以不加括号
与Iterator接口的关系
-
前面说过任何一个对象的Symbol.iterator方法等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象.由于Generator函数就是遍历器生成函数,因此可以把Generator函数赋值给对象的Symbol.iterator属性,从而使该对象具有Iterator接口
-
Generator 函数执行后,返回一个遍历器对象。该对象本身也具有 Symbol.iterator 属性,执行后返回自身
function* gen(){ // some code } var g = gen(); g[Symbol.iterator]() === g // true
next方法的参数
-
yield表达式本身没有返回值,或者说总是返回undefined.next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
-
举个栗子
//第一个例子 function* f() { for(var i = 0; true; i++) { var reset = yield i; if(reset) { i = -1; } } } var g = f(); g.next() // { value: 0, done: false } 第一次执行,yield输出i=0,没有返回值,reset为undefined g.next() // { value: 1, done: false } 第二次执行,yield输出i=1,没有返回值,reset为undefined g.next(true) // { value: 0, done: false } 第三次执行,带有参数true,true被当作第二次执行的返回值,也就是在第二次next中返回了true,因此进入if,将i置为-1,然后i++.所以这次执行时yield输出0 --------------------------------------- //第二个例子 function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var a = foo(5); a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true} var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true } //分析,关于a的调用不作解释,没有传入参数,y,z都为undefined,运算后为NaN //关于b,第一次调用结果为6,第二次调用传入参数为12,也就是将第一次调用中的yield(x+1)返回值设为12,运算后y=12 //第三次调用传入13,也就是将第二次调用中的yield(y/3)返回值设为13,即z=13,因此x+y+z=5+24+13=42
for…of循环
-
可以自动遍历Generator函数生成的Iterator对象,且不需要调用next方法.除了for…of循环以外,扩展运算符(…),解构赋值和Array.from方法内部调用的都是遍历器接口,都可以将Generator函数返回的Iterator 对象,作为参数
function *foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v); } // 1 2 3 4 5 //会依次显示5个yield的值,因为一旦next方法返回的对象中的done属性为true,for...of循环就会终止,且不包含该返回对象,因此return 6 不会显示出来 //因为对象本身没有Iterator接口,因此可以通过Generator函数为它添加接口 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) { //也可以写为for(let [key,value] of objectEntries(jane)) console.log(`${key}: ${value}`); }