es6 Generator函数的语法

1,概念

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同

执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态

形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield语句,定义不同的内部状态(yield在英语里的意思就是“产出”)

function* create(){
    yield '1';
    yield '2';
    return 'over';
}
var n = create();
//Generator函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号

//不同的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象
//必须调用遍历器对象的next方法,使得指针移向下一个状态
//也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield语句(或return语句)为止
//Generator函数是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行

console.log(n.next());//Object {value: "1", done: false}
console.log(n.next());//Object {value: "2", done: false}
console.log(n.next());//Object {value: "over", done: true}
console.log(n.next());//Object {value: undefined, done: true}

//第三次调用,Generator函数从上次yield语句停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。
//next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),
//done属性的值true,表示遍历已经结束。
function * foo(x, y) { ··· }

function *foo(x, y) { ··· }

function* foo(x, y) { ··· }

function*foo(x, y) { ··· }
ES6没有规定, function 关键字与函数名之间的星号,写在哪个位置。这导致写法都能通过

由于Generator函数仍然是普通函数,所以一般的写法是上面的第三种,即星号紧跟在function关键字后面。本书也采用这种写法。

2,yield

由于Generator函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield语句就是暂停标志。
需要注意的是,yield语句后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为JavaScript提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
Generator函数可以不用yield语句,这时就变成了一个单纯的暂缓执行函数。
function* foo(){
    console.log('走起!');
}
var g = foo();
setTimeout(function(){
    g.next();
},3000);
//上面代码中,函数f如果是普通函数,在为变量generator赋值时就会执行。
//但是,函数f是一个 Generator 函数,就变成只有调用next方法时,函数f才会执行。

//另外需要注意,yield语句只能用在 Generator 函数里面,用在其他地方都会报错。
var arr = [1,[[2,3],4],[5,6]];
var flg = function* (a){
    var len = a.length;
    for(var i=0;i<len;i++){
        var item = a[i];
        if(typeof item !== 'number'){
            yield* flg(item);
        }else{
            yield item;
        }
    }
};
for(let f of flg(arr)){
    console.log(f);//1 2 3 4 5 6
}

//另外,yield语句如果用在一个表达式之中,必须放在圆括号里面。
function* foo(){
    console.log('hello'+yield);//SyntaxError
    console.log('hello'+ yield 123);//SyntaxError

    console.log('hello'+(yield));//正确
    console.log('hello'+(yield 1));//正确
}
//yield语句用作函数参数或放在赋值表达式的右边,可以不加括号
function* f() {
    f(yield 'a', yield 'b'); // OK
    let input = yield; // OK
}

3,与Iterator接口的关系

任意一个对象的 Symbol.iterator 方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。
于Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的Symbol.iterator属性,从而使得该对象具有Iterator接口。
var iterable = {};
iterable[Symbol.iterator] = function* (){
    yield 1;
    yield 2;
    yield 3;
};
console.log([...iterable]);//[1, 2, 3]
//上面代码中,Generator函数赋值给Symbol.iterator属性,
//从而使得myIterable对象具有了Iterator接口,可以被...运算符遍历了。

//Generator函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身
function* f(){
    // to do
}
var g = f();
console.log(g[Symbol.iterator]() === g);//true
//上面代码中,f是一个Generator函数,调用它会生成一个遍历器对象g。
//它的Symbol.iterator属性,也是一个遍历器对象生成函数,执行后返回它自己

4,for...of循环

for...of循环可以自动遍历Generator函数时生成的Iterator对象,且此时不再需要调用next方法

function* f(){
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    return 5;
}
for(let i of f()){
    console.log(i);
}
//1 2 3 4
//上面代码使用for...of循环,依次显示4个yield语句的值。
//这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,
//所以上面代码的return语句返回的5,不包括在for...of循环之中

//下面是一个利用Generator函数和for...of循环,实现斐波那契数列的例子。
function* fibonacci(){
    let [prev,curr] = [0,1];
    for(;;){
       [prev,curr] = [curr,prev+curr];
        yield curr;
    }
}
for(let n of fibonacci()){
    if(n > 1000){
        break;
    }
    console.log(n);
}
//从上面代码可见,使用for...of语句时不需要使用next方法。
利用 for...of 循环,可以写出遍历任意对象(object)的方法。原生的JavaScript对象没有遍历接口,无法使用 for...of 循环,通过Generator函数为它加上这个接口,就可以用了。
function* objectEntries(obj){
    let propKeys = Reflect.ownKeys(obj);
    for(let propKey of propKeys){
        yield [propKey,obj[propKey]];
    }
}

let arr = {
    first:'andy',
    second:'jack'
};
for(let [key,value] of objectEntries(arr)){
    console.log(`${key}:${value}`);
}
//first:andy
//second:jack

//上面代码中,对象arr原生不具备Iterator接口,无法用for...of遍历。
//这时,我们通过Generator函数objectEntries为它加上遍历器接口,就可以用for...of遍历了。
//加上遍历器接口的另一种写法是,
//将Generator函数加到对象的Symbol.iterator属性上面。
function* objectEntries1(obj){
    let propKeys = Object.keys(this);

    for (let propKey of propKeys) {
        yield [propKey, this[propKey]];
    }
}
let arr1 = {
    first:'andy',
    second:'jack'
};
arr1[Symbol.iterator] = objectEntries1;
for(let [key,value] of arr1){
    console.log(`${key}:${value}`);
}
//first:andy
//second:jack

//除了for...of循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。
//这意味着,它们都可以将Generator函数返回的Iterator对象,作为参数。

function* numbers(){
    yield 1;
    yield 2;
    return 3;
    yield 4;
}
//扩展运算符
console.log([...numbers()]);//[1, 2]

//Array.from()
console.log(Array.from(numbers()));//[1, 2]

//解构赋值
let [x,y] = numbers();
console.log(x,y);//1 2

//for...of
for(let n of numbers()){
    console.log(n);
}
//1
//2

5,Generator.prototype.throw()

Generator函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。
var f = function* (){
    try{
        yield ;
    }catch (e){
        console.log('inner '+e);
    }
};
var i = f();
i.next();

try{
    i.throw('a');
    i.throw('b');
}catch (e){
    console.log('outer '+e);
}
//inner a
//inner b
//上面代码中,遍历器对象i连续抛出两个错误。
//第一个错误被Generator函数体内的catch语句捕获。i第二次抛出错误,
//由于Generator函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,
//所以这个错误就被抛出了Generator函数体,被函数体外的catch语句捕获。

//throw方法可以接受一个参数,该参数会被catch语句接收,建议抛出Error对象的实例。

var l = function* (){
    try {
        yield ;
    }catch (e){
        console.log(e);
    }
};
var n = l();
n.next();
n.throw(new Error('wrong!'));
//Error: wrong!
//注意,不要混淆遍历器对象的throw方法和全局的throw命令。
//上面代码的错误,是用遍历器对象的throw方法抛出的,而不是用throw命令抛出的。
//后者只能被函数体外的catch语句捕获。

var m = function* (){
    while (true){
        try{
            yield ;
        }catch (e){
            if(e!='a'){
                throw e;
            }
            console.log('inner '+e);
        }
    }
};
var n1 = m();
n1.next();
try{
    throw new Error('a');
    throw new Error('b');
}catch (e){
    console.log('outer '+e);
}
//outer Error: a
//上面代码之所以只捕获了a,是因为函数体外的catch语句块,捕获了抛出的a错误以后,
//就不会再继续try代码块里面剩余的语句了。
//如果Generator函数内部没有部署try...catch代码块,
//那么throw方法抛出的错误,将被外部try...catch代码块捕获
var f = function* (){
    while (true){
        yield ;
        console.log('inner '+e);
    }
};
var i = f();
i.next();
try{
    i.throw('a');
    i.throw('b');
}catch (e){
    console.log('outer '+e);
}
//outer a
//上面代码中,Generator函数g内部没有部署try...catch代码块,
//所以抛出的错误直接被外部catch代码块捕获

//如果Generator函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行
var g = function* g(){
    yield console.log('h');
    yield console.log('w');
};
var i1 = g();
i1.next();
i1.throw();
//h
//Uncaught undefined

//throw方法被捕获以后,会附带执行下一条yield语句。也就是说,会附带执行一次next方法。

var g1 = function* g1(){
    try{
        yield console.log('a');
    }catch (e){

    }
    yield console.log('b');
    yield console.log('c');
};
var n = g1();
console.log(n.next());
n.throw();
console.log(n.next());
//上面代码中,g.throw方法被捕获以后,自动执行了一次next方法,所以会打印b。
//另外,也可以看到,只要Generator函数内部部署了try...catch代码块,
//那么遍历器的throw方法抛出的错误,不影响下一次遍历。

6,Generator.prototype.return()

Generator函数返回的遍历器对象,还有一个 return 方法,可以返回给定的值,并且终结遍历Generator函数。
function* g(){
    yield 1;
    yield 2;
    yield 3;
}
var f = g();
console.log(f.next());//Object {value: 1, done: false}
console.log(f.return('foo'));//Object {value: "foo", done: true}
console.log(f.next());//Object {value: undefined, done: true}
//上面代码中,遍历器对象g调用return方法后,返回值的value属性就是return方法的参数foo。
//并且,Generator函数的遍历就终止了,返回值的done属性为true,以后再调用next方法,done属性总是返回true。

//如果return方法调用时,不提供参数,则返回值的value属性为undefined
function* g1(){
    yield 1;
    yield 2;
    yield 3;
}
var f1 = g1();
console.log(f1.next());//Object {value: 1, done: false}
console.log(f1.return());//Object {value: undefined, done: true}

//如果Generator函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行。
function* num(){
    yield 1;
    try{
        yield 2;
        yield 3;
    }finally {
        yield 4;
        yield 5;
    }
    yield 6;
}
var f2 = num();
console.log(f2.next());//Object {value: 1, done: false}
console.log(f2.next());//Object {value: 2, done: false}
console.log(f2.return(7));//Object {value: 4, done: false}
console.log(f2.next());//Object {value: 5, done: false}
console.log(f2.next());//Object {value: 7, done: true}
//上面代码中,调用return方法后,就开始执行finally代码块,然后等到finally代码块执行完,再执行return方法。

7,yiled*语句

如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。
function* foo(){
    yield 'a';
    yield 'b';
}
function* bar(){
    yield 'x';
    foo();
    yield 'y';
}
for(let i of bar()){
    console.log(i);
}
//x
//y
//上面代码中,foo和bar都是 Generator 函数,在bar里面调用foo,是不会有效果的。
这个就需要用到 yield* 语句,用来在一个 Generator 函数里面执行另一个 Generator 函数。
function* foo(){
    yield 'a';
    yield 'b';
}
function* bar(){
    yield 'x';
    yield* foo() ;
    yield 'y';
}
for(let i of bar()){
    console.log(i);
}
//x
//a
//b
//y
//从语法角度看,如果yield命令后面跟的是一个遍历器对象,需要在yield命令后面加上星号,
//表明它返回的是一个遍历器对象。这被称为yield*语句。
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* 后面的Generator函数(没有 return 语句时),不过是 for...of 的一种简写形式,完全可以用后者替代前者。反之,则需要用 var value = yield* iterator 的形式获取 return 语句的值。

如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员。
function* g(){
    yield* ['a','b','c'];
}
console.log(g().next());//Object {value: "a", done: false}
//上面代码中,yield命令后面如果不加星号,返回的是整个数组,加了星号就表示返回的是数组的遍历器对象。

//实际上,任何数据结构只要有Iterator接口,就可以被yield*遍历。
let arr = (function*(){
        yield 'hello';
        yield* 'hello';
})();
console.log(arr.next().value);//hello
console.log(arr.next().value);//h
console.log(arr.next().value);//e
//上面代码中,yield语句返回整个字符串,yield*语句返回单个字符。
//因为字符串具有Iterator接口,所以被yield*遍历。

function* foo(){
    yield 2;
    yield 3;
    return 'foo';
}
function* bar(){
    yield 1;
    var v = yield* foo();
    console.log('v '+v);
    yield 4;
}
var f = bar();
console.log(f.next());//Object {value: 1, done: false}
console.log(f.next());//Object {value: 2, done: false}
console.log(f.next());//Object {value: 3, done: false}
console.log(f.next());//'v foo' Object {value: 4, done: false}
console.log(f.next());//Object {value: undefined, done: true}
//上面代码在第四次调用next方法的时候,屏幕上会有输出,这是因为函数foo的return语句,向函数bar提供了返回值

function* genF(){
    yield 'a';
    yield 'b';
    return 'over';
}
function* logF(obj){
    let result = yield* obj;
    console.log(result);
}
[...logF(genF())];//over
yield* 命令可以很方便地取出嵌套数组的所有成员。
function* num(n){
    if(Array.isArray(n)){
        for(let i=0;i<n.length;i++){
            yield* num(n[i]);
        }
    }else{
        yield n;
    }
}
const n = ['a',['b','c'],['d']];
for(let x of num(n)){
    console.log(x);
}
//a
//b
//c
//d

8,作为对象的属性的Generator函数

//如果一个对象的属性是Generator函数,可以简写成下面的形式。
let obj = {
    *init(){
        //to do
    }
};
//init属性前面有一个星号,表示这个属性是一个Generator函数
//它的完整形式如下,与上面的写法是等价的
let obj = {
    init:function*(){
        //to do
    }
}

9,Gnerator函数中的this

Generator函数总是返回一个遍历器,ES6规定这个遍历器是Generator函数的实例,也继承了Generator函数的prototype对象上的方法
function* f(){

}
f.prototype.sayHello = function(){
    return 'hi';
};
let obj = f();
console.log(obj instanceof f);// true
console.log(obj.sayHello());// hi
//Generator函数g返回的遍历器obj,是g的实例,而且继承了g.prototype。
//但是,如果把g当作普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象

function* f1(){
    this.a = 10;
}
let obj1 = f1();
console.log(obj1.a);//undefined
//上面代码中,Generator函数f1在this对象上面添加了一个属性a,但是obj1对象拿不到这个属性

//Generator函数也不能跟new命令一起用,会报错
function* F(){
    yield this.x = 2;
    yield this.y = 3;
}
new F();//Uncaught TypeError: F is not a constructor
//new命令跟构造函数F一起使用,结果报错,因为F不是构造函数

如何解决呢?

下面是一个变通方法。首先,生成一个空对象,使用call方法绑定Generator函数内部的this。这样,构造函数调用以后,这个空对象就是Generator函数的实例对象了

function* F(){
    this.a = 1;
    yield this.b = 2;
    yield this.c = 3;
}
var obj = {};
var f = F.call(obj);

console.log(f.next());//Object {value: 2, done: false}
console.log(f.next());//Object {value: 3, done: false}
console.log(f.next());//Object {value: undefined, done: true}

console.log(obj.a);//1
console.log(obj.b);//2
console.log(obj.c);//3
//上面代码中,首先是F内部的this对象绑定obj对象,然后调用它,返回一个Iterator对象。
//这个对象执行三次next方法(因为F内部有两个yield语句),完成F内部所有代码的运行。
//这时,所有内部属性都绑定在obj对象上了,因此obj对象也就成了F的实例。
上面代码中,执行的是遍历器对象 f ,但是生成的对象实例是 obj ,有没有办法将这两个对象统一呢?

一个办法就是将obj换成F.prototype

function* F(){
    this.a = 1;
    yield this.b = 2;
    yield this.c = 3;
}

var f = F.call(F.prototype);

console.log(f.next());//Object {value: 2, done: false}
console.log(f.next());//Object {value: 3, done: false}
console.log(f.next());//Object {value: undefined, done: true}

console.log(f.a);//1
console.log(f.b);//2
console.log(f.c);//3
//再将F改成构造函数,就可以对它执行new命令了。

function* bar(){
    this.a = 1;
    yield this.b = 2;
    yield this.c = 3;
}
function Foo(){
    return bar.call(bar.prototype);
}
var foo = new Foo();

console.log(foo.next());
console.log(foo.next());
console.log(foo.next());

console.log(foo.a);//1
console.log(foo.b);//2
console.log(foo.c);//3

























  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值