Generator函數

本系列屬於阮一峯老師所著的ECMAScript 6 入門學習筆記javascript


概念

Generator函數是ES6提供的一種異步編程解決方案。形式上,Generator函數是一個普通函數,可是有兩個特徵:function關鍵字與函數名之間有一個星號;函數體內部使用yield表達式,定義不一樣的內部狀態(yield表意產出)。java

function* helloWorldGenerator(){
  yield 'hello'
  yield 'world'
  return 'ending'
}
var hw = helloWorldGenerator()
// Generator函數和普通函數同樣調用,可是調用後函數並不執行,返回一個指向內部狀態的指針對象,即遍歷器對象(Iterator Object)

hw.next() // {value: 'hello',done:false}
hw.next() // {value: 'world',done:false}
hw.next() // {value: 'ending',done:true}
hw.next() // {value: undefined,done:true}
// 每次調用next方法,執行Generator函數,依次返回yeild表達式的值,執行到return語句(若沒有return語句就執行到結束)
yield表達式

Generator函數返回遍歷器對象,只有調用next方法纔會遍歷下一個狀態,yield表達式就是暫停函數執行的暫停標誌。es6

Generator函數能夠不用yield表達式,這時就變成一個單純的暫緩執行函數編程

function* f(){
  console.log('done!')
}

var generator = f() // 函數不會當即執行,只有調用next方法纔會執行

setTimeout(function(){
  generator.next()
},2000)

yield表達式若是在另外一個表達式中,必須放在圓括號內數組

function* demo(){
  console.log('Hello' + yield) // SyntaxError
  console.log('Hello' + (yield)) // OK
}

// yield表達式用做函數參數或放在賦值表達式的右邊,能夠不加括號
function* demo(){
  foo(yield 'a',yield 'b') // OK
  let input = yield // OK
}
與Iterator接口的關係

任何一個對象的Symbol.iterator方法,等於該對象的遍歷器生成函數,調用該函數會返回該對象的一個遍歷器對象。因爲Generator函數就是遍歷器生成函數,所以能夠把Generator函數賦值給Symbol.iterator屬性,從而使得該對象具備iterator接口。數據結構

var myIterator = {}
myIterator[Symbol.iterator] = function* (){
  yield 1
  yield 2
  yield 3
}
[...myIterator] // [1,2,3]
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() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

// 每次運行到yield表達式,變量reset總被賦值undefined,當next帶上參數以後,遍歷reset就被賦值爲true
for...of循環

for...of循環能夠自動遍歷Generator函數生成的Iterator對象,且不用再調用next方法異步編程

function* foo(){
  yield 1
  yield 2
  yield 3
  yield 4
  return 5
}
for(let v of foo()){
  console.log(v)
}
// 1 2 3 4

// for...of循環遇到next方法返回對象的done爲true就會停止,因此return返回的5不在循環之中

// 除了for...of循環外,拓展運算符...、解構賦值和Array.from均可以將Generator函數返回的Iterator對象做爲參數
function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}

// 拓展運算符
[...numbers()] // [1,2]

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

// 解構賦值
let [x,y] = numbers()
x // 1
y // 2

// for...of循環
for(let n of numbers()){
  console.log(n) // 1 2
}
Generator.prototype.throw()

Generator函數返回的遍歷器對象有一個throw方法,能夠在函數體外拋出錯誤,而後在函數體內捕獲函數

var g = function* (){
  try{
    yield
  }catch(e){
    console.log('內部捕獲',e)
  }
}

var i = g()
i.next()

// throw方法能夠接收一個參數,改參數會被catch語句接收,建議拋出Error對象實例
try {
  i.throw('a')
  i.throw('b')
}catch(e){
  console.log('外部捕獲',e)
}
// 內部捕獲 a
// 外部捕獲 b

// throw方法被捕獲後會附帶執行下一條yield表達式,也就是說會附帶執行一次next方法
Generator.prototype.return()

return方法返回給定的值,而且終結遍歷Generator函數學習

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

// 若是Generator函數內部有try...finally代碼塊,那麼return方法會推遲到finally代碼執行完以後再執行
function* numbers(){
  yield 1
  try{
    yield 2
    yield 3
  }finally{
    yield 4
    yield 5
  }
  yield 6
}
var g = numbers()
g.next() // {value:1,done:false}
g.next() // {value:2,done:false}
g.return(7) // {value:4,done:false}
g.next() // {value:5,done:false}
g.next() // {value:7,done:true}
yield* 表達式

在Generator函數內調用另外一個Generator函數,默認狀況下是沒有效果的,這時候就要用到yield*表達式

function* inner(){
  yield 'hello!'
}

function* outer1(){
  yield 'open'
  yield inner()
  yield 'close'
}

var gen = outer1()
gen.next().value // 'open'
gen.next().value // 返回一個遍歷器對象
gen.next().value // 'close'

function* outer2(){
  yield 'open'
  yield* inner()
  yield 'close'
}

var gen = outer2()
gen.next().value // 'open'
gen.next().value // 'hello'
gen.next().value // ’close'

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
  }
}

function* gen(){
  yield* ['a','b','c']
}
gen().next() // {value:'a',done:false}
// 若不加星號返回的是整個數組,加了就表示返回的是數組的遍歷器對象。實際上任何具備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 it = bar();

it.next()
// {value: 1, done: false}
it.next()
// {value: 2, done: false}
it.next()
// {value: 3, done: false}
it.next();
// "v: foo"
// {value: 4, done: false}
it.next()
// {value: undefined, done: true}

//被代理的Generator函數foo有return語句,那麼就會向代理它的Generator函數bar返回數據,而且繼續執行next方法
做爲對象屬性的Generator函數
let obj = {
  * myGeneratorMethod(){
    ...
  }
}
Generator函數的this
// 生成空對象,使用call方法綁定Generator函數內部的this
function* F(){
  this.a = 1
  yield this.b = 2
  yield this.c = 3
}
var obj = {}
var f = F.call(obj)
// 調用三次next方法完成F內部全部代碼的運行,將全部內部屬性綁定在obj對象上
f.next() // {value:2,done:false}
f.next() // {value:3,done:false}
f.next() // {value:undefined,done:true}
// obj對象編程了F的實例
obj.a // 1
obj.b // 2
obj.c // 3

// 將F改爲構造函數,能夠執行new命令
function* gen(){
  this.a = 1
  yield this.b = 2
  yield this.c = 3
}

function F(){
  return gen.call(gen.prototype)
}

var f = new F()
f.next() // {value:2,done:false}
f.next() // {value:3,done:false}
f.next() // {value:undefined,done:true}

f.a // 1
f.b // 2
f.c // 3
相關文章
相關標籤/搜索