1. 程式人生 > 其它 >ES6 之 Generator 函式

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: Doe
Reflect.ownKeys() 返回物件所有的屬性,不管屬性是否可列舉,包括 Symbol。

jane 原生是不具備 Iterator介面無法通過 for... of遍歷。用了 Generator 函式加上了 Iterator介面,就可以遍歷 jane 物件了。