參考 來源《ecmascript6 入門》generator部分javascript
形式上,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正由於能夠有多個,能夠返回多個值,因此generator函數能夠返回一系列的值,這也就是它名稱的來歷(generator英語意思爲生成器)promise
任意一個對象的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
函數返回的遍歷器對象,都有一個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()
若是在 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**
函數,能夠簡寫成下面的形式
let obj = { * myGeneratorMethod() { ··· } }; 等同於 let obj = { myGeneratorMethod: function* () { // ··· } };
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
函數最大的做用能夠用做異步任務的封裝(因爲它的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能夠往裏傳值。