简介
- 把它理解成一个状态机,封装了多个内部状态,执行Generator函数会返回一个遍历器对象,可以依次遍历Generator函数内部的每一个状态。
- 定义时function与函数名之间有一个星号
- 内部使用yield(产出)语句,定义不同的内部状态
- 调用遍历器对象的next方法,使指针移向下一个状态
调用Generator函数后,该函数并不执行,而是返回一个指向内部状态的指针对象,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield语句(或return语句)为止。Generator函数是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function* helloWorldGenerator() { 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 }
|
yield语句
- 遍历器对象的next遇到yield语句,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值
- 下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句
- 如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值,若无则返回undefined
- yield语句如果用在一个表达式之中,必须放在圆括号里面,作函数参数或赋值表达式的右边除外
yield与return区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield语句,所以Generator函数可以返回一系列的值,即生成器之意
yield句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。第一次使用next方法时,不能带有参数,V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。
1 2 3 4 5 6 7 8 9 10 11 12
| function* f() { for(var i=0; true; i++) { var reset = yield i; if(reset) { i = -1; } } } var g = f(); g.next() g.next() g.next(true)
|
通过next方法的参数,向Generator函数内部输入值
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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')
|
与Iterator接口的关系
- 执行Generator函数后返回Iterator对象
for…of循环、扩展运算符(…)、解构赋值和Array.from方法内部调用的,都是使用遍历器接口,即可以自动遍历Generator函数
1 2 3 4 5 6 7 8 9 10 11
| 扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。 let z = { a: 3, b: 4 }; let n = { ...z }; n 扩展运算符可以用于合并两个对象。 let ab = { ...a, ...b }; let ab = Object.assign({}, a, b);
|
Generator.prototype.return()
- 遍历器对象的return方法,可以返回给定的值,并且终结遍历Generator函数
1 2 3 4 5 6 7 8 9 10 11
| function* gen() { yield 1; yield 2; yield 3; } var g = gen(); g.next() // { value: 1, done: false } g.return("foo") // { value: "foo", done: true },return不提供参数,则返回值的vaule属性为undefined g.next() // { value: undefined, done: true }
|
yield*语句
- 用来在一个Generator函数里面执行另一个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 35
| function* foo() { yield 'a'; yield 'b'; } 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); }
|
yield*语句等同于在Generator函数内部,部署一个for...of循环
,如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员,还有字符串之类有Iterator接口的数据结构
1 2 3 4 5
| function* gen(){ yield* ["a", "b", "c"]; } gen().next()
|
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 35
| function Tree(left, label, right) { this.left = left; this.label = label; this.right = right; } function* inorder(t) { if (t) { yield* inorder(t.left); yield t.label; yield* inorder(t.right); } } function make(array) { if (array.length == 1) return new Tree(null, array[0], null); return new Tree(make(array[0]), array[1], make(array[2])); } let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]); var result = []; for (let node of inorder(tree)) { result.push(node); } result
|
Generator的一些应用
异步转同步
可以把异步操作写在yield语句里面,等到调用next方法时再往后执行
1 2 3 4 5 6 7 8 9 10 11 12
| function* main() { var result = yield request("http://some.url"); var resp = JSON.parse(result); console.log(resp.value); } function request(url) { makeAjaxCall(url, function(response) { it.next(response); }); } var it = main(); it.next();
|
流程控制
多个任务需要并列执行时,可以采用数组的写法
1 2 3 4 5 6 7
| function* parallelDownloads() { let [text1,text2] = yield [ taskA(), taskB() ]; console.log(text1, text2); }
|
部署iterator接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function* iterEntries(obj) { let keys = Object.keys(obj); for (let i=0; i < keys.length; i++) { let key = keys[i]; yield [key, obj[key]]; } } let myObj = { foo: 3, bar: 7 }; for (let [key, value] of iterEntries(myObj)) { console.log(key, value); }
|