js 中的Generator 函數

語法上

首先能夠把它理解成,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表達式

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
}

next 方法的參數

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()

image.png
因爲next方法的參數表示上一個yield表達式的返回值,因此在第一次使用next方法時,傳遞參數是無效的。V8 引擎直接忽略第一次使用next方法時的參數,只有從第二次使用next方法開始,參數纔是有效的。從語義上講,第一個next方法用來啓動遍歷器對象,因此不用帶有參數。對象

Generator.prototype.throw()

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

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

var i = g();
i.next();
//外部拋出錯誤:
i.throw('a');

image.png

注意點:
1.throw方法拋出的錯誤要被內部捕獲,前提是必須至少執行過一次next方法。
2.throw方法被捕獲之後,會附帶執行下一條yield表達式。也就是說,會附帶執行一次next方法。
3.Generator 函數體外拋出的錯誤,能夠在函數體內捕獲;反過來,Generator 函數體內拋出的錯誤,也能夠被函數體外的catch捕獲。
4.一旦 Generator 執行過程當中拋出錯誤,且沒有被內部捕獲,就不會再執行下去了。若是此後還調用next方法,將返回一個value屬性等於undefined、done屬性等於true的對象,即 JavaScript 引擎認爲這個 Generator 已經運行結束了。繼承

Generator.prototype.return()

Generator 函數返回的遍歷器對象,還有一個return方法,能夠返回給定的值,而且終結遍歷 Generator 函數。

yield* 表達式

語法角度看,若是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 函數

若是一個對象的屬性是 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對象。

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
相關文章
相關標籤/搜索