一篇 Generator 详解

希沃ENOW大前端javascript

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

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

本文做者:程序员

大大大正名片.jpg

前言

遥想当初面试的时候,被问到经典问题ES6都有啥,年纪轻轻的我搜刮了满脑子,除了水就是水。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的基本用法吧!markdown

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}
复制代码

写法上:

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

从打印结果上来看:

  1. 普通函数一口气打印了1,2,3,4。
  2. 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的基本使用以及简单的实现的原理,方便在特殊的场景中解决问题。

相关文章
相关标签/搜索