什么是Generator函数

文/anMoo韩魔

图片来源网络

Generator函数

语法上面看,Generator函数可以理解成一个状态机,封装了多个内部状态,执行一个Generator函数就会返回一个遍历器对象,可以依次遍历Generator函数内部的每一个状态。

特征:

  • function命令与函数名之间有一个星号
  • 函数体内使用yield语句定义不同的内部状态
  • 可以交出函数的执行权,即暂停执行
function* statusgenerator(){
    yield:'A';
    yield:'B';
    return 'ending';
};
var sg = statusGenerator(); //undefined

上面的sg是调用statusgenerator函数后返回的遍历器对象,所以相对具有next()方法。

var sg = statusGenerator(); //undefined
sg
//statusGenerator{[[GeneratorStatus]]:"suspended",[[GeneratorReceiver]]:"Window,[[GeneratorLocation]]:Object"}
sg.next();
//Object{value:"A",done:false}
sg.next();
//Object{value:"B",done:false}
sg.next();
//Object{value:"ending",done:true}

当使用next方法的时候,函数内部的指针就会从那个上次停止的地方开始指向下一条yield就停止。这样可以使Generator函数分段执行,调用next()方法就可以恢复执行。

因为Generator函数可以用来返回每个状态的结果,所以可以达到状态机的效果。

yield

Generator函数返回的遍历器对象只有调用next方法才会遍历下一个内部状态,所以其实是提供了一种可以暂停执行的函数,yield语句就是暂停的标志,也就是异步两个阶段的分界线。

这样做的最大优点:代码的写法非常像同步操作,如果去除yield命令,简直一摸一样。

遍历器对象的next方法的运行逻辑:

  • 遇到yield就暂停执行后面的操作,并将紧跟yield后的表达式的值作为返回的对象的value属性值。
  • 下一次调用next方法时在继续往下执行,直到遇到下一个yield。
  • 如果没有遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值作为返回对象的value属性值。
  • 如果函数没有return语句,则返回的对象value属性值为undefined。

需要注意的是

  1. Generator函数可以不用yield语句,yield语句不能用在普通函数中,会报错。
  2. yield语句如果用在一个表达式中,必须放在圆括号里面。如果用作函数参数或者用于赋值表达式的右边,可以不加括号。所以容易引起误解的地方应该把yield用圆括号括起来。
function* g(){
    console.log('hello' + (yield 'world'));
}

Generator函数和Symbol.iterator方法

任何一个对象的Symbol.iterator方法等于该对象的遍历器对象生成函数,也就是说在调用这个函数的时候会返回该对象的一个遍历器对象。
因为Generator函数调用的时候就是返回一个遍历器函数。当你需要对一个对象部署Symbol.iterator方法时,可以使用Generator函数。

function* g(){};
var gen = g();
gen[Symbol.iterator]() === gen // true

Next()方法传参

Yield语句本身没有返回值,或者说是返回值是undefined,next方法可以带一个参数,该参数会当做上一条yield语句的返回值。

function* f(){
    for(var i = 0;true; i++){
        var reset = yield i;
        if(reset){
            i = -1
        }
    }
}
var g = f();

g.next(); // Object{value:0,done:false}
g.next(); // Object{value:1,done:false}
g.next(true); // Object{value:0,done:false}

最后next的参数true作为reset=yield i的返回值,所以i就等于-1。

从这个例子可以看出,Generator函数从暂停到恢复运行,其上下文状态是不变的,通过next方法的参数可以在Generator函数开始运行后继续向函数体内部注入值,也就是说,在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数的行为。

Generator运用For...of循环

Generator返回的是遍历器,所以可以用For... of循环Generator函数,可以不用再调用next方法。

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

function* f(){
    yield 'a';
    yield 'b';
    yield 'c';
    return 'd'
}
for(v of f()){
    console.log(v)
}
//返回的依次是:a,b,c,undefined

Generator.prototype.throw()

可以在函数体外抛出错误,然后在函数体内捕获。

var g = function(){
    while(true){
        try{
            yield;
        }catch(e){
            if(e!='a') throw e;
            console.log('内部捕获',e)
        }
    }
}

i=g();
i.next();// Object{value: undefined, done: false}
i.throw('a') // 内部捕获 a
// Object{value: undefined, done: false}

注意:

  • 不要混淆遍历器对象的throw方法和全局的throw方法
  • 如果Generator内部没有部署try...catch代码块,那么throw方法抛出的错误将被外部try...catch代码捕获
  • 如果Generator内部部署了try...catch代码块,那么遍历器的throw方法抛出的错误不影响下一次遍历。

Generator.prototype.return()

Generator函数返回的遍历器对象,有一个return方法,可以返回给定的值,并且终结Generator函数的遍历。如果return方法调用时不提供参数,则返回值的value属性为undefined。

var gen = function(){
    yield 1;
    yield 2;
    yield 3;
};
var g = gen();
g.next();  // Object{value:1,done:false}
g.return('hello')  // Object{value:"hello",done:true}
g.next();  // Object{value:undefined,done:true}

Yield*语句

如果在Generator函数内部调用另外一个Generator函数,默认情况下是没有效果的。需要使用yield*语句,在一个Generator函数里执行另外一个Generator函数。
这样相当于在外面的Generator函数中遍历另外一个Generator函数,然后yield

var inner = function* (){
    yield 'a';
    yield 'b';
}

var gen = function* (){
    yield 1;
    yield 2;
    yield* inner();
    yield 3;
}

var g = gen();
g.next() // 1 false
g.next() // 2 false
g.next() // "a" false
g.next() // '"b' false
g.next() // 3 false
g.next() // undefined true

上下两种方法得到的结果是一致的。

var gen = function* (){
    yield 1;
    yield 2;
    for(v of inner()){
        yield v;
    };
    yield 3;
}

var g = gen();
g.next() // 1 false
g.next() // 2 false
g.next() // "a" false
g.next() // '"b' false
g.next() // 3 false
g.next() // undefined true

如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此会遍历数组成员。

实际上,任何数据结构只要有Iterator接口,就可以用yield*遍历。

如果yield命令后面跟的是一个遍历器对象,那么需要在yield命令后面加上星号,表明返回的是一个遍历器对象,这个被称为yield*语句。相当于for...of的一种简写形式。

Generator函数可以作为对象的属性,简写成如下形式:

let obj = {
    *myGeneratorMethod(){
        yield;
    }
}

Generator函数总是返回一个遍历器,这个遍历器是Generator函数的实例,继承了Generator函数prototype对象上的方法。

Generator函数的用途

  • 异步操作的同步化表达
  • 控制流管理
  • 部署Iterator接口
  • 作为数据结构

注:以上所有内容都是本人的学习笔记和总结,仅供学习和参考,如果有遗漏或者不当的地方请谅解,请勿转载。

@anMoo韩魔
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • 在此处先列下本篇文章的主要内容 简介 next方法的参数 for...of循环 Generator.prototy...
    醉生夢死阅读 1,436评论 3 8
  • 简介 基本概念 Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。本章详细介绍...
    呼呼哥阅读 1,067评论 0 4
  • 本文作者就是我,简书的microkof。如果您觉得本文对您的工作有意义,产生了不可估量的价值,那么请您不吝打赏我,...
    microkof阅读 23,720评论 16 78
  • 异步编程对JavaScript语言太重要。Javascript语言的执行环境是“单线程”的,如果没有异步编程,根本...
    呼呼哥阅读 7,297评论 5 22
  • Given a positive integer n, break it into the sum of at l...
    Jeanz阅读 272评论 0 0