Generator函数(三)
Generator.prototype.return()
1.Generator函数返回的遍历器对象还有一个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:false}
g.next();//{value:undefined,done:false}
//上面的代码中,遍历器对象g调用return方法之后,返回的也是一个对象,而且这个对象的value属性就是return方法的参数foo.同时,Generator函数的遍历终止,返回值的done属性为true,以后再调用next方法,done属性总是返回true。如果return方法调用时不提供参数,则返回值的value属性为undefined。
2.如果Generator函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行。
function* numbers(){
yield 1;
try{
yield 2;
yield 3;
}finally{
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next(); //value:1
g.next();//value:2
g.return(7);//value:4
g.next();//value:5
g.next();//value:7
yield*语句
如果在Generator函数内部调用另一个Generator函数,默认情况下是没有效果的,必须要使用yield*语句
1.这个语句的作用:主要是用来在一个Generator函数中使用另一个Generator函数。
function* foo(){
yield 'a';
yield 'b';
}
function* bar(){
yield 'x';
foo(); // 将这里修改成yield* foo(),就会有效果
yield 'y';
}
for(let v of bar()){
console.log(v);
}
//"x"
//"y"在这里面直接用yield语句是没有任何效果的
2.从语法角度看,如果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);
}
//"Greeting"
//"hello"
//"bye!"
//"ok,bye"
总结下来:(1)运行结果,就是使用一个遍历器遍历了多个Generator函数,有递归的效果。
(2)yield* 语句等同于在Generator函数内部部署一个for...of循环。
(3)yield* 语句后面还可以接数组,因为数组原生支持遍历器,因此会遍历数组成员
(4)yield* 语句后面,只要是有Iterator接口,都可以用yield*遍历。
3.如果被代理的Generator函数有return语句,那么可以向代理它的Generator函数返回数据。
function* foo(){
yield 2;
yield 3
return "foo";
};
function* bar(){
yield 1;
var v = yield* foo();
console.log("v:" + v);
yield 4;
}
var it = bar();
it.next();//value:1
it.next();//value:2
it.next();//value:3
it.next();//"v:foo",value:4这里不是太明白,姑且认为是因为foo函数被bar()函数代理的缘故
4.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)yield* 语句可以用来遍历完全二叉树
//用构造函数构造一个完全二叉树
//3个参数分别是左子树,当前节点,右子树。
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);
console.log(node);
}
作为对象属性的Generator函数
1.如果一个对象的属性是一个Generator函数,则可以这样写:
let obj = {
* myGeneratorMethod(){
...
}
}
//也可以这样写:
let obj = {
myGeneratorMethod:function* (){}
};
Generator函数的this
1.Generator函数总是返回一个遍历器,ES6规定这个遍历器是Generator函数的实例,它也继承了Generator函数的prototype对象上的方法。
2.如果只是把Generator函数当作普通的构造函数,并不会有任何效果,因为Generator函数返回的始终是遍历器对象,而不是this对象
function* g(){
this.a = 11;
}
let obj = g();
obj.a //undefined
3.还有如果使用new命令,这是不能生成F的实例,因为F返回的是一个内部指针。
function* F(){
yield this.x = 2;
yield this.y = 3;
}
上面的方法是没有任何效果的,想要把Generator函数当作正常的构造函数来使用,可以采用下面的变通方法。
function* F(){
yield this.x = 2;
yield this.y = 3;
}
var obj = {};
var f = F.bind(obj)();//将obj和this绑定在一起。
//再次调用next方法
f.next();
f.next();
f.next();
obj //{x:2,y:3}
Generator函数推导
1.利用函数推导,可以进行惰性求值,如果需要将一个数组中的每个成员进行平方,如果用到函数推导,那么就只会在用到的时候,才会占用系统资源。
否则会先定义一个数组,这时候系统会被占用很大一部分资源。
let generator = function* () {
for(let i=0;i<6;i++){
yield i;
}
}
let squared = ( for (n of generator()) n*n);
//等同于
//let squared = Array.from(generator()).map( n => n*n);
console.log(...squared);
//0 1 4 9 16 25
Generator函数推导是对数组结构的一种模拟,其最大的优点就是惰性求值,即直到真正用到的时候才会求值,这样可以保证效率。
含义
1.Generator与状态机
Generator函数很好的实现了两个或者两个状态以上的切换过程,而且是合作式的。
var clock = function*(_){
while(true){
yield _;
console.log('Tick');
yield _;
console.log('Tock');
}
}
2.Generator与协程
传统的“子例程”采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数。协程与其不同,多个线程(单线程情况下即多个函数)可以并行执行,但只有一个线程(或函数)处于正在运行的状态,其他线程(或函数)都处于暂停态,线程(或函数)之间可以交换执行权。也就是说,一个线程(或函数)执行到一半,可以暂停执行,将执行权交给另一个线程(或函数),等到稍后收回执行权时再恢复执行。这种可以并行执行、交换执行权的线程(或函数),就称为协程。
从实现上看,在内存中子例程只使用一个栈,而协程是同时存在多个栈,但只有一个栈是在运行态。也就是说,协程是以多占用内存为代价实现多任务的并行运行。
3.协程与普通线程的差异
普通的线程是抢占式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。而且Generator函数是ES6对协程的实现,但属于不完全实现。如果将Generator函数当作协程,完全可以将多个需要协作的任务写成Generator函数,他们之间用yield语句交换控制权。
Generator函数的主要应用
1.异步操作的同步化表达
例如:ajax操作,
function* main(){
var result = yield request("http:fff.url");//这里通过调用next方法来传递给result
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url){
makeAjaxCall(url,function(response){
it.next(response);//将这个response传递给result,这里必须在next方法加上response参数,因为yield语句构成的表达式本身是没有值的,总是等于undefined。
})//这个函数主要是用来获取应答数据
}
var it = main();
it.next();
2.控制流管理:同步运行和异步运行
3.部署Iterator接口:利用Generator函数可以给任意对象部署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];//这里通过yield
}
}
let myObj = {foo:3,bar:7};
for(let [key,value] of iterEntries(myObj)){
console.log(key,value);//这里用for...of循环来遍历Generator函数
}
最后注意:
1.yield 语句后面可以接普通函数,也可以正常执行。
2.yield* 语句主要是用来在一个Generator函数中执行另一个Generator函数。主要可以实现递归。