Generator函数是ES6出现的一种异步操作实现方案。
异步即代码分两段,但是不是连续执行,第一段执行完后,去执行其他代码,等条件允许,再执行第二段。
同步即代码连续执行。
Generator函数是一种遍历器生成函数;运行后返回一个遍历器对象。
函数内部可以看作一个状态机;可以通过遍历器的next方法遍历函数内部的各个状态。
Generator函数不是构造函数!不能使用new命令生成实例。否则报错!
但是生成的实例是Generator函数的实例对象。
function* testGen() { yield 1; } const gen = testGen(); console.log(gen instanceof testGen); //生成的对象是Generator的实例 console.log(gen.__proto__ === testGen.prototype); // 生成的实例继承原型上的属性和方法 // 运行结果 true
true
构造函数本质是通过new命令,返回this对象,即实例对象。
但是Generator函数不返回this,它返回遍历器。通过直接执行返回。
此时函数内部的this是函数执行时所在的对象,即window(非严格模式,严格模式下是undefined)
<script type="module"> function* testGen() { console.log(this); } const gen = testGen(); gen.next(); </script> // 运行结果如下(严格模式) undefined
如果想要生成的遍历器对象可以访问this的属性和方法,可以使用call或者apply方法绑定this到其原型对象上。
function* testGen() { this.a = 5; return this; } const gen = testGen.call(testGen.prototype); const thisObj = gen.next().value;// gen.next().value === testGen.prototype console.log(thisObj.a); // 5
1.作为函数声明
function* fnName() { // function和函数名称之间有一个*, 通常紧跟function后 yield 表达式; // 函数内部使用yield命令 return val; //表示最后一个状态 } const myGenerator = fnName(); //生成遍历器对象;相当于指向内部状态的指针对象
其中,*是Generator函数的标识;yield(“产生”)是状态生成命令。
2. Generator函数作为对象的属性声明
const obj = { *testGen() { ... } } // 相当于 const obj = { testGen: function* () { ... } }
调用Generator函数后,会返回一个遍历器对象;但是此时函数内部代码并不执行。
遍历器对象可以通过自身的next方法访问Generator函数内部的状态。
yield命令除了表示生成状态,还表示暂停标识,即next方法遇到yield命令即停止运行。
所以next()方法表示从代码起始或者上一次停止的位置运行到下一个yield或者return或者代码结束。
next方法返回结果格式如下:
{value: 值, done: true/false} // value是当次运行的状态,即yield命令后面的表达式的值 // done表示是否遍历结束
想继续执行就要继续调用next方法,如果函数内部没有return,则一直运行到代码结束;
返回结果最后是:
{value: undefined, done: true} //继续调用next方法,会一直返回该值
如果遇到return value,则return语句即表示遍历结束。
返回结果是:
{value: value, done: true} //继续调用next方法,返回 {value: undefined, done: true} // 继续调用next,一直返回该值
示例:
function* testGen() { yield 1; return 2; } const gen = testGen(); console.log(gen.next()); console.log(gen.next()); console.log(gen.next()); console.log(gen.next()); // 运行结果如下 {value: 1, done: false} {value: 2, done: true} {value: undefined, done: true} {value: undefined, done: true}
?可以遍历遍历器状态的遍历方法有
function* testGen() { yield 1; yield 2; yield 3; } const gen = testGen(); /****1. 通过扩展运算符...*******/ let result1 = [...gen]; console.log(result1) /****2. 通过for...of遍历*******/ let result2 = []; for(let item of gen) { result2.push(item); } console.log(result2) /****3. 通过数组解构赋值********/ let [...result3] = gen; console.log(result3) /****4. 通过Array.from********/ let result4 = Array.from(gen); console.log(result4) // 运行结果如下: [1,2,3] // 遍历器已经遍历结束,继续遍历后面会全部是[] [] [] []
function* testGen() { yield 1; yield 2; yield 3; } const gen1 = testGen(); /****1. 通过扩展运算符...*******/ let result1 = [...gen1]; console.log(result1) /****2. 通过for...of遍历*******/ const gen2 = testGen(); let result2 = []; for(let item of gen2) { result2.push(item); } console.log(result2) /****3. 通过数组解构赋值********/ const gen3 = testGen(); let [...result3] = gen3; console.log(result3) /****4. 通过Array.from********/ const gen4 = testGen(); let result4 = Array.from(gen4); console.log(result4) // 运行结果如下: [1,2,3] [1,2,3] [1,2,3] [1,2,3]
1. Generator函数中yield表达式可以不存在
此时Generator函数是一个等待next方法启动的暂缓执行函数。
function* testGen() { console.log(‘等待执行‘); } const gen = testGen(); setTimeout(() => { gen.next(); // 2秒后打印‘等待执行‘ }, 2000)
2. yield表达式最近的函数只能是Generator函数,否则声明时就会报错
function* testGen() { [1,2].forEach(function(item) { //回调函数不是Generator函数;可以改为for循环 yield item; //Uncaught SyntaxError: Unexpected identifier }) }
3. yield表达式不是单独使用或者不是在=右侧时,需要将其括起来
function* testGen() { console.log(1 + (yield 1)); }
4. yield表达式本身不返回任何值,或者说返回undefined
function* testGen() { let r1 = yield 1; let r2 = yield 2; console.log(r1, r2); } let gen = testGen(); gen.next(); gen.next(); gen.next(); //遍历结束 // 运行结果如下 undefined undefined
gen.next(value);
带参数的next方法,其中的参数表示上一次yield表达式的返回值。
所以第一次next方法如果有参数,参数无效,因为第一个next从代码起始执行,之前没有yield表达式。
function* testGen() { let r1 = yield 1; let r2 = yield 2; console.log(r1, r2); } let gen = testGen(); gen.next(‘是啥都没有用‘); gen.next(‘give1‘); //第一个yield返回give1 gen.next(‘give2‘); //第二个yield返回give2 // 运行结果如下 give1 give2
如果想要第一个next方法的参数起作用,可以将Generator函数包装一下
function* testGen() { let r1 = yield 1; let r2 = yield 2; console.log(r1, r2); } function wrapper(fn) { let gen = fn(); gen.next(); return gen; } let gen = wrapper(testGen); gen.next(‘give1‘); //第一个yield返回give1 gen.next(‘give2‘); //第二个yield返回give2 // 运行结果如下 give1 give2
上面的示例也说明,Generator函数的执行进度可以保留。
从遍历器对象上手动抛出异常。
该异常可以被Generator函数内部的try...catch捕获;抛出异常的位置位于遍历器当前状态指针所在的位置。
function* testGen() { try{ yield; } catch(err) { console.log(‘innerErr-->‘,err); } yield 1; } const gen = testGen(); gen.next(); gen.throw(new Error("gen.throw")); // 在第二个yield位置抛出异常,无法捕获 console.log(‘after‘); // 运行结果 innerErr-->Error: gen.throw after
function* testGen() { try{ yield; } catch(err) { console.log(‘innerErr-->‘,err); } yield 1; } const gen = testGen(); gen.next(); gen.next(); // 比上面的示例多了一个next方法 gen.throw(new Error("gen.throw")); // 在第二个yield位置抛出异常,无法捕获 console.log(‘after‘); // 运行结果 Uncaught Error: gen.throw //后面的不再执行
也可以被外部调用该方法的位置的catch捕获。
function* testGen() { yield; yield 1; } const gen = testGen(); gen.next(); try { gen.throw(new Error("gen.throw")); // 在第二个yield位置抛出异常,无法捕获 } catch (error) { console.log(‘outer-->‘,error); } console.log(‘after‘); // 运行结果 outer-->Error: gen.throw after
当内部和外部try...catch同时存在时,错误先被Generator函数内部捕获;
function* testGen() { try { yield; } catch(err) { console.log(‘inner-->‘,err); } yield 1; } const gen = testGen(); gen.next(); try { gen.throw(new Error("gen.throw")); // 在第二个yield位置抛出异常,无法捕获 } catch (error) { console.log(‘outer-->‘,error); } console.log(‘after‘); // 运行结果 inner-->Error: gen.throw after
注意:try...catch相当于一次性错误捕获器,捕获一次错误,后面再有错误需要另外一个捕获器。
诸如数组等可遍历对象本质是本身含有[Symbol.iterator]属性,该属性是一个遍历器生成方法。
使用for...of或者next()或者...进行遍历时,本质上遍历的是[Symbol.iterator]方法。
而Generator方法本身就是一个遍历器生成方法。
所以可以通过将Generator函数赋值给目标对象,使其具有iterator接口。
示例:给不具有iterator接口的普通对象添加iterator接口
// 普通对象没有iterator接口,所以不能使用for..of function* testGen() { let r1 = yield 1; let r2 = yield 2; console.log(r1, r2); return 3; } let obj = { a: 1 } obj[Symbol.iterator] = testGen; for (let item of obj) { // obj添加[Symbol.iterator]之前不能被遍历 console.log(item); //item是内部的状态值 } console.log([...obj]); // 运行结果如下 1 2 undefined undefined //for...of undefined undefined // ...遍历 [1,2]
从上面可知:
for...of不需要手动遍历,会自动遍历生成器内部的所有状态,但是不会遍历done为true的值。
所以上面3不会被遍历。
基础语法:break对于for,while来说都是终止之后的循环;continue终止本次循环
示例: 实现fibonacci数列
function* fibonacci() { let [prev, curr] = [0, 1]; yield curr; while(true) { [prev, curr] = [curr, prev+curr]; yield curr; } } for(let item of fibonacci()) { if (item === 8) { break; } console.log(item); } // 运行结果如下: 1 1 2 3 5
示例:模拟Object.entries()方法
// Object.entries(obj)可以使用for...of遍历 // 说明该方法返回遍历器;写一个方法覆盖Object上的原有方法 Object.entries = function* (obj) { let objKeys = Object.keys(obj); for (let key of objKeys) { console.log(‘---selfdefined---‘); yield [key, obj[key]]; } } let obj = {a:1,b:2}; for(let [key, value] of Object.entries(obj)) { console.log([key,value]) } // 运行结果如下: ---selfdefined--- [‘a‘,1] ---selfdefined--- [‘b‘,2]
原文:https://www.cnblogs.com/lyraLee/p/11789402.html