首先能夠把它理解成,Generator 函數是一個狀態機,封裝了多個內部狀態。執行 Generator 函數會返回一個遍歷器對象,也就是說,Generator 函數除了狀態機,仍是一個遍歷器對象生成函數。返回的遍歷器對象,能夠依次遍歷 Generator 函數內部的每個狀態。函數
Generator 函數是一個普通函數,可是有兩個特徵。this
一是,function關鍵字與函數名之間有一個星號; 二是,函數體內部使用yield表達式,定義不一樣的內部狀態(yield在英語裏的意思就是「產出」)。
Generator 函數的調用方法與普通函數同樣,也是在函數名後面加上一對圓括號。不一樣的是,調用 Generator 函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象,也就是上一章介紹的遍歷器對象(Iterator Object)。咱們必須調用遍歷器對象的next方法,使得指針移向下一個狀態。也就是說,每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)爲止。換言之,Generator 函數是分段執行的,yield表達式是暫停執行的標記,而next方法能夠恢復執行spa
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator(); hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }
調用 Generator 函數,返回一個遍歷器對象,表明 Generator 函數的內部指針。之後,每次調用遍歷器對象的next方法,就會返回一個有着value和done兩個屬性的對象。value屬性表示當前的內部狀態的值,是yield表達式後面那個表達式的值;done屬性是一個布爾值,表示是否遍歷結束。prototype
yield表達式與return語句既有類似之處,也有區別。類似之處在於,都能返回緊跟在語句後面的那個表達式的值。區別在於每次遇到yield,函數暫停執行,下一次再從該位置繼續向後執行,而return語句不具有位置記憶的功能。一個函數裏面,只能執行一次(或者說一個)return語句,可是能夠執行屢次(或者說多個)yield表達式。正常函數只能返回一個值,由於只能執行一次return;Generator 函數能夠返回一系列的值,由於能夠有任意多個yield。從另外一個角度看,也能夠說 Generator 生成了一系列的值,這也就是它的名稱的來歷(英語中,generator 這個詞是「生成器」的意思)。代理
語法注意點:
1.yield表達式只能用在 Generator 函數裏面
2.yield表達式若是用在另外一個表達式之中,必須放在圓括號裏面
3.yield表達式用做函數參數或放在賦值表達式的右邊,能夠不加括號。
例如:指針
function* demo() { foo(yield 'a', yield 'b'); // OK let input = yield; // OK }
yield表達式自己沒有返回值(就是說let a=yield ;會返回undefined),或者說老是返回undefined。next方法能夠帶一個參數,該參數就會被看成上一個yield表達式的返回值 (注意,是整個表達式的返回值而不僅是yield 後方的值,例如 let a=yield.......... 參數會是a 的值而且會覆蓋表達式以前的值)。code
function* f() { for(var i = 0; true; i++) { var reset = yield i; console.log(reset); if(reset) { i = -1; } } } var g = f(); g.next()
因爲next方法的參數表示上一個yield表達式的返回值,因此在第一次使用next方法時,傳遞參數是無效的。V8 引擎直接忽略第一次使用next方法時的參數,只有從第二次使用next方法開始,參數纔是有效的。從語義上講,第一個next方法用來啓動遍歷器對象,因此不用帶有參數。對象
Generator 函數返回的遍歷器對象,都有一個throw方法,能夠在函數體外拋出錯誤,而後在 Generator 函數體內捕獲。blog
var g = function* () { try { yield; } catch (e) { console.log('內部捕獲到錯誤', e); } }; var i = g(); i.next(); //外部拋出錯誤: i.throw('a');
注意點:
1.throw方法拋出的錯誤要被內部捕獲,前提是必須至少執行過一次next方法。
2.throw方法被捕獲之後,會附帶執行下一條yield表達式。也就是說,會附帶執行一次next方法。
3.Generator 函數體外拋出的錯誤,能夠在函數體內捕獲;反過來,Generator 函數體內拋出的錯誤,也能夠被函數體外的catch捕獲。
4.一旦 Generator 執行過程當中拋出錯誤,且沒有被內部捕獲,就不會再執行下去了。若是此後還調用next方法,將返回一個value屬性等於undefined、done屬性等於true的對象,即 JavaScript 引擎認爲這個 Generator 已經運行結束了。繼承
Generator 函數返回的遍歷器對象,還有一個return方法,能夠返回給定的值,而且終結遍歷 Generator 函數。
語法角度看,若是yield表達式後面跟的是一個遍歷器對象,須要在yield表達式後面加上星號,代表它返回的是一個遍歷器對象。這被稱爲yield表達式(我的理解yield 主要用做遍歷具備遍歷器(Iterator)接口的對象或函數)。
如:
用來在一個 Generator 函數裏面執行另外一個 Generator 函數。
function* foo() { yield 'a'; yield 'b'; } // 直接調用沒有效果 function* bar() { yield 'x'; foo(); yield 'y'; } // 使用yield* foo(); 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); }
若是被代理的 Generator 函數有return語句,那麼就能夠向代理它的 Generator 函數返回數據。
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 函數,能夠簡寫成下面的形式。
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對象。
function* g() { this.a = 11; } let obj = g(); obj.next(); obj.a // undefined
Generator 函數也不能直接跟new命令一塊兒用
function* F() { yield this.x = 2; yield this.y = 3; } new F()
變通方法
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(); // 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