一篇 Generator 详解

您所在的位置:网站首页 generator协程 一篇 Generator 详解

一篇 Generator 详解

#一篇 Generator 详解| 来源: 网络整理| 查看: 265

希沃ENOW大前端

公司官网:CVTE(广州视源股份)

团队:CVTE旗下未来教育希沃软件平台中心enow团队

本文作者:

大大大正名片.jpg

前言

遥想当初面试的时候,被问到经典问题ES6都有啥,年纪轻轻的我搜刮了满脑子,除了水就是水。

image.png

那么ES6都有啥呢,很快脑子里就有let和const、模板字符串、解构赋值、展开运算符、箭头函数等等。说了这些也许足够让面试官展开发挥了,但是优秀的掘金er怎么可能止步于此?

image.png

为了让面试官眼前一亮,es6还有这些新特性:模块化、Symbol、Set和Map数据结构、Proxy代理和Reflect反射、Generator等等,说完面试官估计眼睛开始有光了,那么说了就要懂,懂了才不怕被面试官问,虽然有点冷门,俗话说技多不压身,今天就一起来看看Generator吧!

image.png

generator是什么

generator即生成器,是ES6规范带来的新内容,在generator能够让我们在函数执行时任意地方暂停,在后续遇到合适的时机需要使用这个函数时继续执行。以往我们遇到的函数都是一口气执行到底,而generator的特点就是让函数执行到中间“刹车”,在需要它的时候接着执行。下面从一个案例来看看generator的基本用法吧!

image.png

常见的javascript函数:

function fun() { console.log(1) console.log(2) console.log(3) } function run() { console.log(4) } fun() run() // 结果: 1 2 3 4

使用generator函数:

function* funG() { yield console.log(1) yield console.log(2) yield console.log(3) } function run() { console.log(4) } const iter = funG() iter.next() run() iter.next() iter.next() iter.next() // 结果: 1 4 2 3 {value: undefined, done:true}

写法上:

generator相对于普通函数在function后面多加了*号。 在每个我们需要中断执行的语句前加了yield,通过yield来控制函数执行。

从打印结果上来看:

普通函数一口气打印了1,2,3,4。 generator打印结果明显不同,当调用generator函数的时候并不是立即执行,返回的是一个生成器内部指针对象iter,通过调用.next()方法移动指针对象到下一个yield,执行表达式,返回表达式结果并暂停自身。每执行一次,都会返回一个包含value和done属性的对象,value为当前表达式的值,done是boolean值,当done的值为true的时候,表示生成器执行完成。

知道generator的基本用法了,但还不够,为了加深面试官的印象,我们可以从generator设计出发点聊聊协程!

image.png

generator与协程

既然要聊协程,首先得知道协程是什么吧!​

简单来说协程就像单身程序员小王敲代码,老大给了他一个项目A,小王收到立马开码;​

小王项目A做到一半,老大说有个项目B时间赶,赶紧来干项目B;​

于是小王停止开发项目A,着手开干项目B;​

项目B开发一段时间后,小王回来接着干项目A。​

这就是协程,那么项目B做完了?也许没有。​

image.png

看完了协程的案例,聪明的你应该想到了协程跟generator之间的关系!没错,generator就是协程在js上的实现。通过generator,我们可以在单线程的JavaScript里使用协程!

generator的特性用法

generator本身作为异步编程的解决方案,可以用来解决异步任务。除此之外还可以有更灵活的用法!那便是在函数执行过程中传入参数,以及获取该段表达式输出结果!

上文提到generator每次执行next()方法都会返回一个对象:{ value, done },通过value,我们可以获取该段表达式的返回结果,另外,next还可以接受参数,利用这一特性,我们可以随时传入参数!​

function* run(name) { let who = yield name + ' Allen'; return who } let flashMan = run('Barry') flashMan.next() // { value: 'Barry Allen', done: false } // 传入参数 flashMan.next('Arrow') // { value: 'Allow', done: true }

在第一个next()调用时,不传入参数,默认将name的参数值'Barry'与' Allen'组合字符串,这个值被yield返回。

在第二个next()调用时,传入参数'Arrow',这个值被变量who接收,因此返回value属性的值为'Arrow',也就是who的值。

不仅如此,generator还可以在函数运行时,捕获函数体外抛出的错误。

function* gen(x){ try { var y = yield x + 2; } catch (e){ console.log(e); } return y; } var g = gen(1); g.next(); g.throw('error'); // error generator的简单实现

generator的原理是转化为switch-case来实现的,先从一个简单的案例入手。

function* funG() { yield 1 yield 2 yield 3 } const iter = funG() iter.next() // {value: 1, done: false} iter.next() // {value: 2, done: false} iter.next() // {value: 3, done: false} iter.next() // {value: undefined, done: true}

generator的实现需要一个函数,这个函数可以多次调用,每次返回一个结果,还可以传入参数,那么switch-case就是很好的选择。

function funGen(count) { switch(count) { case 1: return {value: 1, done: false}; case 2: return {value: 2, done: false}; case 3: return {value: 3, done: false}; case 'done': return {value: undefined, done: true} } } funGen(1) // {value: 1, done: false}; funGen(2) // {value: 2, done: false}; funGen(3) // {value: 3, done: false}; funGen('done') // {value: undefined, done: true}

从结果上来看是我们想要的结果,但是距离generator还有很大的差距,接下来创建一个函数,这个函数返回一个对象,通过调用这个对象来帮我们执行14~17行的语句。

function funGen(count) { switch(count) { case 1: return {value: 1, done: false}; case 2: return {value: 2, done: false}; case 3: return {value: 3, done: false}; case 'done': return {value: undefined, done: true} } } const gen = function() { let count = 0 return { next: function() { ++count count = count > 3 ? 'done' : count return funGen(count) } } } const test = gen() test.next() // {value: 1, done: false} test.next() // {value: 2, done: false} test.next() // {value: 3, done: false} test.next() // {value: undefined, done: true}

到目前为止,都需要我们手动处理函数上下文里的count的值的变化来决定返回结果,所以我们需要一个对象来保存函数上下文也就是count的值。

function example(context) { while(1) { context.pre = context.next switch(context.pre) { case 1: context.next = 2 return 1; case 2: context.next = 3 return 2; case 3: context.next = 'done' return 3; case 'done': return context.end() } } } const gen = function() { return { next: function() { value = context.done ? undefined : funGen(context) done = context.done return { value, done } } } } const context = { pre: 1, next: 1, done: false, end: function end() { this.done = true } } const test = gen() test.next() // {value: 1, done: false} test.next() // {value: 2, done: false} test.next() // {value: 3, done: false} test.next() // {value: undefined, done: false}

通过一个新对象context来记录函数上下文的初始状态pre,以及下一个状态next,done记录是否到最终点。而end便是修改done为结束状态true。

funGen每次运行next()结束后都会将运行状态保存到context中,方便下次运行时获取。​

总结

gernerator作为es6的新特性,在后来被更方便好用的async await代替,但是generator独特的特性可以让我们在函数执行的过程中传递参数获取结果,使得函数调用变得更加灵活。作为一个开发者,我们有必要了解一下gernerator的基本使用以及简单的实现的原理,方便在特殊的场景中解决问题。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3