简介
- 把它理解成一个状态机,封装了多个内部状态,执行Generator函数会返回一个遍历器对象,可以依次遍历Generator函数内部的每一个状态。
- 定义时function与函数名之间有一个星号
- 内部使用yield(产出)语句,定义不同的内部状态
- 调用遍历器对象的next方法,使指针移向下一个状态
调用Generator函数后,该函数并不执行,而是返回一个指向内部状态的指针对象,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield语句(或return语句)为止。Generator函数是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行
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方法用来启动遍历器对象,所以不用带有参数。
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 }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
通过next方法的参数,向Generator函数内部输入值
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
return 'result';
}
let genObj = dataConsumer();
genObj.next();
// Started
genObj.next('a')
// 1. a
genObj.next('b')
// 2. b
与Iterator接口的关系
- 执行Generator函数后返回Iterator对象
for...of循环、扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是使用遍历器接口,即可以自动遍历Generator函数
扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
扩展运算符可以用于合并两个对象。
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
Generator.prototype.return()
- 遍历器对象的return方法,可以返回给定的值,并且终结遍历Generator函数
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函数
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*语句等同于在Generator函数内部,部署一个for...of循环
,如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员,还有字符串之类有Iterator接口的数据结构
function* gen(){
yield* ["a", "b", "c"];
}
gen().next() // { value:"a", done:false }
// 下面是二叉树的构造函数,
// 三个参数分别是左树、当前节点和右树
function Tree(left, label, right) {
this.left = left;
this.label = label;
this.right = right;
}
// 下面是中序(inorder)遍历函数。
// 由于返回的是一个遍历器,所以要用generator函数。
// 函数体内采用递归算法,所以左树和右树要用yield*遍历
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
// ['a', 'b', 'c', 'd', 'e', 'f', 'g']
Generator的一些应用
异步转同步
可以把异步操作写在yield语句里面,等到调用next方法时再往后执行
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); //因为yield语句构成的表达式,本身是没有值的,必须加上response参数
});
}
var it = main();
it.next();
流程控制
多个任务需要并列执行时,可以采用数组的写法
function* parallelDownloads() {
let [text1,text2] = yield [
taskA(),
taskB()
];
console.log(text1, text2);
}
部署iterator接口
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);
}
// foo 3
// bar 7