Generator函数
基本概念
英文意思为 “生成器”。
-
generator函数是es6提供的一种异步编程解决方案,语法行为与传统函数完全不同。从状态上,首先我们把他理解成一种状态机,封装了多个内部状态。另外执行generator函数胡返回一个遍历对象,就是说,generator函数还是一个遍历器对象生成函数,可以一次遍历函数中的每一个状态。
-
generator函数的两个特征
- function命令与函数名之间有一个星号;
- 函数体内部使用yield语句定义不同的内部状态(yield为“产出”的意思)。
调用generator函数后,该函数并不执行,返回的也不是函数的运行结果,而是一个指向内部状态的指针对象,也就相当于遍历器对象。下一步必须调用遍历器对象的next方法,使得指针移向下一个状态。就是说每次调用next方法,内部指针就从函数头部或上次停留的地方开始执行,直到遇到吓一跳yield语句或是return语句时停止。yield语句是generator函数的暂停执行,next是恢复执行。
yield语句
是generator函数的暂停标志。
- next方法的运行逻辑:
- 遇到yield语句就暂停执行后面的操作,并将紧跟在yield后面表达式的值作为返回的对象的value属性值。
- 下次调用next方法时再继续往下执行,直到遇到下一个yield语句;
- 如果没有遇到新的yield语句,就一直运行到函数结束,直到遇到return语句为止,并将return语句后面的表达式的值作为返回对象的value属性值;
- 如果该函数没有return语句,则返回对象的value属性值为undefined
注意: yield语句为javascript提供了手动的 “惰性求值”。
yield语句与return语句有相似之处也有区别,相似之处在于都返回紧跟在后面的值,区别在于return语句不具备记忆功能。
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);
}
这个例子将会报错,因为forEach方法的参数是个普通的函数,但是里面使用了yield语句,普通函数里面是不允许是使用yield语句的。
那么我们来修改一下
var arr = [1, [[2,3],4],[5,6]];
var flat = function* (a){
var length = a.length;
for(var i =0; i < length; i++){
var item = a[i];
if(typeof item !== 'number'){
yield* flat(item);
}else{
yield item;
}
}
};
for(var f of flat(arr)){
console.log(f);
}// 1, 2, 3, 4, 5, 6
- 与Iterator接口的关系
任意一个对象的Symbol.iterator方法等于该对象的遍历器对象生成函数,调用该函数会返回该对象的一个遍历器对象。
遍历器对象本身也有Symbol.iterator方法,执行后返回自身。
function* gen(){
//some code
}
var g = gen();
console.log(g[Symbol.iterator]() === g);
gen是一个generator函数,调用它会生成一个遍历器对象g,他的Symbol.iterator属性也是一个遍历器对象生成函数,执行后返回自己。
yield* 语句
如果在generator函数内部调用另一个generator函数,默认情况下是没有效果的。
-
yield*语句,专门用来在一个generator函数里面执行另一个generator函数。
function* foo(){ yield 'a'; yield 'b'; } function* bar(){ yield 'x'; yield* foo(); yield 'y'; } for(let v of bar()){ console.log(v); }
上面将会返回x, a, b, y。如果直接调用foo函数的话,将不会有什么效果。
-
如果yield命令后面没有星号,将返回一个遍历器对象,加上星号后将会返回遍历对象的具体值。yield* 语句相当于在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命令将会返回全部都的数组成员,yield* 则返回单个字符:即数组的第一个成员。
-
如果generator函数有return语句,那么将会返回相应的数据,如果return()不带任何参数,则相应位置返回的值为undefined。
-
yield* 命令可以很方便的取出嵌套数组的所有成员。
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
Generator函数的this
Generator函数总是返回一个遍历器,这个遍历器是generator函数的实例。
function* g(){
this.a = 11;
}
let obj = g();
console.log(obj.a);//undefined
上面的代码中,函数g总是返回遍历器对象,而不是this对象,这时使用new命令就不能生成对应函数的实例,因为函数返回的是一个内部指针。如果想让generator函数能有正常的构造函数,就可以采用:先生成一个空对象,使用bind方法绑定generator函数内部的指针,这样,构造函数调用之后这个空对象就是generator函数的实例对象了。
function* F(){
yield this.x = 2;
yield this.y = 3;
}
var obj = {};
var f = F.bind(obj)();
f.next();
f.next();
f.next();
console.log(obj);//Object{value: 2, done: false}
通过generator函数部署Ajax操作
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();
上面的main函数就是通过AJAX获取到的数据,makeAjaxCall函数的next方法中必须加上response参数,因为yield语句构成的表达式本身是没有值的,总是等于undefined。