es6 generator函數

參考 來源《ecmascript6 入門》generator部分javascript

認識generator函數

形式上,generator函數有兩個特色:一是function關鍵字與函數名之間有一個*。二是函數體內使用yield語句,以下代碼。(yield在英語中意思就是 產出)java

function* helloWorld(){
    yield ‘hello’;
    yield ‘world’;
    return ‘ending’;
}

var hw=helloWorld();

調用執行,調用generator函數和調用普通函數的形式同樣,沒有區別,好比上面helloWorld()
可是內部的執行與普通函數是徹底不一樣,調用generator函數以後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象,也就是遍歷器對象。也就是說generator函數仍是一個遍歷器對象生成函數。返回的遍歷器對象能夠依次遍歷generator函數內部的每個狀態。node

它是怎麼遍歷的呢?。遍歷器對象每次調用next方法,內部指針就從函數頭部或者上一次停下來的的地方開始執行,遇到yield語句暫停並返回一個對象,下一次調用next,遇到下一個yield暫停並返回一個對象(對象擁有value,和done屬性)。value的值就是yield語句的值,done屬性表示遍歷是否結束(false沒有結束,true結束)。git

上面示例代碼用調用4次next:es6

第一次調用next,generator函數開始執行,遇到第一個yield暫停,而且返回一個對象,value =hello,done=false表示還遍歷尚未結束。github

第二次調用next,從上次暫停的位置開始執行,遇到下一個yield暫停,並返回一個對象。。json

第三次調用next,返回value爲return的值,done爲true表示遍歷結束。api

第四次調用next,generator函數已經運行完畢,返回value爲undefined,done爲true。數組

yield和return;

yield語句與return語句既有類似之處 ,也有區別。類似之處在於均可以返回緊跟在其後邊的表達式的值。區別在於每次遇到yield,函數暫停,下一次在從該位置向後執行,而return語句沒有此位置記憶功能,一個函數裏面只能執行一次,而yield正由於能夠有多個,能夠返回多個值,因此generator函數能夠返回一系列的值,這也就是它名稱的來歷(generator英語意思爲生成器)promise

與Iterator接口的關係

任意一個對象的iterator接口都是部署在了Symbol.iterator屬性,因爲generator函數就是遍歷器生成函數,因此能夠直接把它賦值給Symbol.iterator,從而使的該對象具備Iterator接口。

示例:

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]

說明:代碼中generator函數賦給了myIterable對象的Symbol.iterator屬性,使的該對象具備iterator接口,能夠 被()運算符遍歷。爲何是這樣?(…)三個點這裏叫作擴展運算符,它的執行是調用了遍歷器方法(它能夠將一個數組轉爲用逗號分割的序列,能夠用於函數調用傳參),這裏就是generator函數,而後返回一個遍歷器對象,而後重複調用它的next方法。其實不僅有擴展運算符,for..of循環的執行也是調用的iterator接口方法,也就是說只有部署了iterator接口的數據集合纔可使用for...of,擴展運算符遍歷。

Generator.prototype.throw()

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

示例:

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

var i = g();
i.next();

try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕獲', e);
}
// 內部捕獲 a
// 外部捕獲 b

上面代碼遍歷器對象連續拋出兩個錯誤,第一個被generator函數體內的catch捕獲。第二個因爲generator函數體內的catch已經執行過了,因此被外面的catch捕獲。若是generator函數體內沒有try...catch...語句,那麼就會被外面的catch語句捕獲。若是都沒有try...catch...,那麼程序報錯。

5.Generator.prototype.return()

yield*語句

若是在 Generator 函數內部,調用另外一個 Generator 函數,默認狀況下是沒有效果的。yield*語句能夠用來在一個 Generator 函數裏面執行另外一個 Generator 函數。

function* foo() {
  yield 'a';
  yield 'b';
}

function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

// 等同於
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 等同於
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"

從語法角度看,若是yield命令後面跟的是一個遍歷器對象,須要在yield命令後面加上星號,代表它返回的是一個遍歷器對象。這被稱爲yield*語句。

let delegatedIterator = (function* () {
  yield 'Hello!';
  yield 'Bye!';
}());

let delegatingIterator = (function* () {
  yield 'Greetings!';
  yield* delegatedIterator;
  yield 'Ok, bye.';
}());

for(let value of delegatingIterator) {
  console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye.」

yield*後面的Generator函數(沒有return語句時),等同於在Generator函數內部,部署一個for...of循環。

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

上面代碼,yield* 執行的是一個遍歷器,for...of...循環的也是一個遍歷器,因此for...of...返回yield value時等同於yield*

兩個日常會用到的示例:

1)遍歷嵌套的數組:

function* iterTree(tree) {
  if (Array.isArray(tree)) {
    for(let i=0; i < tree.length; i++) {
      yield* iterTree(tree[i]);
    }
  } else {
    yield tree;
  }
}

const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];

for(let x of iterTree(tree)) {
  console.log(x);
}
// a
// b
// c
// d
// e

2)對於狀態的控制:

var clock = function*() {
  while (true) {
    console.log('Tick!');
    yield;
    console.log('Tock!');
    yield;
  }
};

做爲對象屬性的generator

若是一個對象的屬性是**Generator**函數,能夠簡寫成下面的形式

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

等同於

let obj = {
  myGeneratorMethod: function* () {
    // ···
  }
};

Generator函數的this

Generator函數老是返回一個遍歷器,ES6規定這個遍歷器是Generator函數的實例,也繼承了Generator函數的prototype對象上的方法。

function* g() {}

g.prototype.hello = function () {
  return 'hi!';
};

let obj = g();

obj instanceof g // true
obj.hello() // 'hi!'

上面代碼代表,Generator函數g返回的遍歷器obj,是g的實例,並且繼承了g.prototype。可是,若是把g看成普通的構造函數,並不會生效,由於g返回的老是遍歷器對象,而不是this對象。因此若是在generator函數內使用this,obj對象訪問不到。

那麼,有沒有辦法讓Generator函數返回一個正常的對象實例,既能夠用next方法,又能夠得到正常的this

function* F() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}
var f = F.call(F.prototype);

f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3

上面代碼:首先使用call函數將F函數的this綁定到F.prototype;而f仍是那個遍歷器對象是F函數的實例,又能夠繼承F.prototype的屬性,因此也就能夠訪問F.prototype表明的this的屬性了。

Generator函數的應用

generator函數最大的做用能夠用做異步任務的封裝(因爲它的yield命令特性,能夠暫停和恢復執行)。而以前javascript對於異步的實現主要就是 回調函數,事件監聽,promise等。

示例:

var fetch = require('node-fetch');

function* gen(){
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}

上面代碼中,Generator 函數封裝了一個異步操做,該操做先讀取一個遠程接口,而後從 JSON 格式的數據解析信息。就像前面說過的,這段代碼很是像同步操做,除了加上了yield命令。

var g = gen();
var result = g.next();

result.value.then(function(data){
  return data.json();
}).then(function(data){
  g.next(data);
});

上面代碼中,首先執行 Generator 函數,獲取遍歷器對象,而後使用next方法(第二行),執行異步任務的第一階段。因爲Fetch模塊返回的是一個 Promise 對象,而這個對象被yield返回到了它的value屬性中,所以要用.value.then方法調用then方法。成功後 return數據參數data能夠被第二個then方法中接受。而第二次調用then方法傳入的data又傳回了gen函數給了變量result。value往出傳值,next能夠往裏傳值。

相關文章
相關標籤/搜索