一篇 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的基本使用以及簡單的實現的原理,方便在特殊的場景中解決問題。

相關文章
相關標籤/搜索