参考 来源《ecmascript6 入门》generator部分javascript
形式上,generator
函数有两个特色:一是function
关键字与函数名之间有一个*。二是函数体内使用yield
语句,以下代码。(yield在英语中意思就是 产出)java
function* helloWorld(){ yield ‘hello’; yield ‘world’; return ‘ending’; } var hw=helloWorld();
调用执行,调用generator
函数和调用普通函数的形式同样,没有区别,好比上面helloWorld()
。
可是内部的执行与普通函数是彻底不一样,调用generator函数以后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象。也就是说generator函数仍是一个遍历器对象生成函数。返回的遍历器对象能够依次遍历generator函数内部的每个状态。node
它是怎么遍历的呢?。遍历器对象每次调用next方法,内部指针就从函数头部或者上一次停下来的的地方开始执行,遇到yield语句暂停并返回一个对象,下一次调用next,遇到下一个yield暂停并返回一个对象(对象拥有value,和done属性)。value的值就是yield语句的值,done属性表示遍历是否结束(false没有结束,true结束)。git
上面示例代码用调用4次next:es6
第一次调用next,generator
函数开始执行,遇到第一个yield暂停,而且返回一个对象,value =hello,done=false
表示还遍历尚未结束。github
第二次调用next
,从上次暂停的位置开始执行,遇到下一个yield暂停,并返回一个对象。。json
第三次调用next
,返回value为return的值,done为true表示遍历结束。api
第四次调用next
,generator函数已经运行完毕,返回value
为undefined,done
为true。数组
yield
语句与return
语句既有类似之处 ,也有区别。类似之处在于均可以返回紧跟在其后边的表达式的值。区别在于每次遇到yield,函数暂停,下一次在从该位置向后执行,而return语句没有此位置记忆功能,一个函数里面只能执行一次,而yield正由于能够有多个,能够返回多个值,因此generator函数能够返回一系列的值,这也就是它名称的来历(generator英语意思为生成器)promise
任意一个对象的iterator接口都是部署在了Symbol.iterator属性,因为generator函数就是遍历器生成函数,因此能够直接把它赋值给Symbol.iterator,从而使的该对象具备Iterator接口。
示例:
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable] // [1, 2, 3]
说明:代码中generator
函数赋给了myIterable
对象的Symbol.iterator
属性,使的该对象具备iterator
接口,能够 被(…
)运算符遍历。为何是这样?(…)三个点这里叫作扩展运算符,它的执行是调用了遍历器方法(它能够将一个数组转为用逗号分割的序列,能够用于函数调用传参),这里就是generator函数,而后返回一个遍历器对象,而后重复调用它的next方法。其实不仅有扩展运算符,for..of
循环的执行也是调用的iterator接口方法,也就是说只有部署了iterator接口的数据集合才可使用for...of,扩展运算符遍历。
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); } // 内部捕获 a // 外部捕获 b
上面代码遍历器对象连续抛出两个错误,第一个被generator
函数体内的catch
捕获。第二个因为generator函数体内的catch已经执行过了,因此被外面的catch捕获。若是generator函数体内没有try...catch...
语句,那么就会被外面的catch语句捕获。若是都没有try...catch...,那么程序报错。
5.Generator.prototype.return()
若是在 Generator
函数内部,调用另外一个 Generator 函数,默认状况下是没有效果的。yield*语句能够用来在一个 Generator 函数里面执行另外一个 Generator 函数。
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); } // "x" // "a" // "b" // "y"
从语法角度看,若是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); } // "Greetings! // "Hello!" // "Bye!" // "Ok, bye.”
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*
执行的是一个遍历器,for...of...
循环的也是一个遍历器,因此for...of...返回yield value时等同于yield*
。
两个日常会用到的示例:
1)遍历嵌套的数组:
function* iterTree(tree) { if (Array.isArray(tree)) { for(let i=0; i < tree.length; i++) { yield* iterTree(tree[i]); } } else { yield tree; } } const tree = [ 'a', ['b', 'c'], ['d', 'e'] ]; for(let x of iterTree(tree)) { console.log(x); } // a // b // c // d // e
2)对于状态的控制:
var clock = function*() { while (true) { console.log('Tick!'); yield; console.log('Tock!'); yield; } };
若是一个对象的属性是**Generator**
函数,能够简写成下面的形式
let obj = { * myGeneratorMethod() { ··· } }; 等同于 let obj = { myGeneratorMethod: function* () { // ··· } };
Generator
函数老是返回一个遍历器,ES6规定这个遍历器是Generator
函数的实例,也继承了Generator
函数的prototype对象上的方法。
function* g() {} g.prototype.hello = function () { return 'hi!'; }; let obj = g(); obj instanceof g // true obj.hello() // 'hi!'
上面代码代表,Generator
函数g返回的遍历器obj,是g的实例,并且继承了g.prototype
。可是,若是把g看成普通的构造函数,并不会生效,由于g返回的老是遍历器对象,而不是this对象。因此若是在generator
函数内使用this,obj对象访问不到。
那么,有没有办法让Generator
函数返回一个正常的对象实例,既能够用next
方法,又能够得到正常的this
?
function* F() { this.a = 1; yield this.b = 2; yield this.c = 3; } var f = F.call(F.prototype); f.next(); // Object {value: 2, done: false} f.next(); // Object {value: 3, done: false} f.next(); // Object {value: undefined, done: true} f.a // 1 f.b // 2 f.c // 3
上面代码:首先使用call函数将F函数的this绑定到F.prototype;而f仍是那个遍历器对象是F函数的实例,又能够继承F.prototype的属性,因此也就能够访问F.prototype表明的this的属性了。
generator
函数最大的做用能够用做异步任务的封装(因为它的yield命令特性,能够暂停和恢复执行)。而以前javascript
对于异步的实现主要就是 回调函数,事件监听,promise
等。
示例:
var fetch = require('node-fetch'); function* gen(){ var url = 'https://api.github.com/users/github'; var result = yield fetch(url); console.log(result.bio); }
上面代码中,Generator
函数封装了一个异步操做,该操做先读取一个远程接口,而后从 JSON
格式的数据解析信息。就像前面说过的,这段代码很是像同步操做,除了加上了yield
命令。
var g = gen(); var result = g.next(); result.value.then(function(data){ return data.json(); }).then(function(data){ g.next(data); });
上面代码中,首先执行 Generator
函数,获取遍历器对象,而后使用next
方法(第二行),执行异步任务的第一阶段。因为Fetch模块返回的是一个 Promise
对象,而这个对象被yield返回到了它的value属性中,所以要用.value.then方法调用then方法。成功后 return数据参数data能够被第二个then方法中接受。而第二次调用then方法传入的data又传回了gen函数给了变量result。value往出传值,next能够往里传值。