ES6 之 Generator 函式
ES6 新引入了 Generator 函式,可以通過 yield 關鍵字,把函式的執行流掛起,為改變執行流程提供了可能,從而為非同步程式設計提供解決方案。
Generator 函式組成
Generator 有兩個區分於普通函式的部分:
-
一是在 function 後面,函式名之前有個 * ;
-
函式內部有 yield 表示式。
其中 * 用來表示函式為 Generator 函式,yield 用來定義函式內部的狀態。
function* func(){ console.log("one"); yield '1'; console.log("two"); yield '2'; console.log("three"); return '3'; }
執行機制
呼叫 Generator 函式和呼叫普通函式一樣,在函式名後面加上()即可,但是 Generator 函式不會像普通函式一樣立即執行,而是返回一個指向內部狀態物件的指標,所以要呼叫遍歷器物件Iterator 的 next 方法,指標就會從函式頭部或者上一次停下來的地方開始執行。
f.next(); // one // {value: "1", done: false} f.next(); // two // {value: "2", done: false} f.next(); // three // {value: "3", done: true}f.next(); // {value: undefined, done: true}
第一次呼叫 next 方法時,從 Generator 函式的頭部開始執行,先是列印了 one ,執行到 yield 就停下來,並將yield 後邊表示式的值 '1',作為返回物件的 value 屬性值,此時函式還沒有執行完, 返回物件的 done 屬性值是 false。
第二次呼叫 next 方法時,同上步 。
第三次呼叫 next 方法時,先是列印了 three ,然後執行了函式的返回操作,並將 return 後面的表示式的值,作為返回物件的 value 屬性值,此時函式已經結束,多以 done 屬性值為true 。
第四次呼叫 next 方法時, 此時函式已經執行完了,所以返回 value 屬性值是 undefined ,done 屬性值是 true 。如果執行第三步時,沒有 return 語句的話,就直接返回 {value: undefined, done: true}。
函式返回的遍歷器物件的方法
next 方法
一般情況下,next 方法不傳入引數的時候,yield 表示式的返回值是 undefined 。當 next 傳入引數的時候,該引數會作為上一步yield的返回值。
function* sendParameter(){ console.log("start"); var x = yield '2'; console.log("one:" + x); var y = yield '3'; console.log("two:" + y); console.log("total:" + (x + y)); }
next不傳參
var sendp1 = sendParameter(); sendp1.next(); // start // {value: "2", done: false} sendp1.next(); // one:undefined // {value: "3", done: false} sendp1.next(); // two:undefined // total:NaN // {value: undefined, done: true} next傳參 var sendp2 = sendParameter(); sendp2.next(10); // start // {value: "2", done: false} sendp2.next(20); // one:20 // {value: "3", done: false} sendp2.next(30); // two:30 // total:50 // {value: undefined, done: true}
除了使用 next ,還可以使用 for... of 迴圈遍歷 Generator 函式生產的 Iterator 物件。
return 方法
return 方法返回給定值,並結束遍歷Generator 函式。return 方法提供引數時,返回該引數;不提供引數時,返回 undefined 。
function* foo(){ yield 1; yield 2; yield 3; } var f = foo(); f.next(); // {value: 1, done: false} f.return("foo"); // {value: "foo", done: true} f.next(); // {value: undefined, done: true} throw 方法 throw 方法可以再 Generator 函式體外面丟擲異常,再函式體內部捕獲。 var g = function* () { try { yield; } catch (e) { console.log('catch inner', e); } }; var i = g(); i.next(); try { i.throw('a'); i.throw('b'); } catch (e) { console.log('catch outside', e); } // catch inner a // catch outside b
遍歷器物件丟擲了兩個錯誤,第一個被 Generator 函式內部捕獲,第二個因為函式體內部的catch 函式已經執行過了,不會再捕獲這個錯誤,所以這個錯誤就丟擲 Generator 函式體,被函式體外的 catch 捕獲。
yield*表示式
yield* 表示式表示 yield 返回一個遍歷器物件,用於在 Generator 函式內部,呼叫另一個 Generator 函式。
function* callee() { console.log('callee: ' + (yield)); } function* caller() { while (true) { yield* callee(); } } const callerObj = caller(); callerObj.next(); // {value: undefined, done: false} callerObj.next("a"); // callee: a // {value: undefined, done: false} callerObj.next("b"); // callee: b // {value: undefined, done: false} // 等同於 function* caller() { while (true) { for (var value of callee) { yield value; } } }
使用場景
實現 Iterator
為不具備 Iterator介面的物件提供遍歷方法。
function* objectEntries(obj) { const propKeys = Reflect.ownKeys(obj); for (const propKey of propKeys) { yield [propKey, obj[propKey]]; } } const jane = { first: 'Jane', last: 'Doe' }; for (const [key,value] of objectEntries(jane)) { console.log(`${key}: ${value}`); } // first: Jane // last: DoeReflect.ownKeys() 返回物件所有的屬性,不管屬性是否可列舉,包括 Symbol。
jane 原生是不具備 Iterator介面無法通過 for... of遍歷。用了 Generator 函式加上了 Iterator介面,就可以遍歷 jane 物件了。