1. 程式人生 > 實用技巧 >ES6中Generator 函式

ES6中Generator 函式

什麼是generator

Generator 函式有多種理解角度。語法上,首先可以把它理解成,Generator 函式是一個狀態機,封裝了多個內部狀態。

執行 Generator 函式會返回一個遍歷器物件,也就是說,Generator 函式除了狀態機,還是一個遍歷器物件生成函式。返回的遍歷器物件,可以依次遍歷 Generator 函式內部的每一個狀態。

形式上,Generator 函式是一個普通函式,但是有兩個特徵。一是,function關鍵字與函式名之間有一個星號;二是,函式體內部使用yield表示式,定義不同的內部狀態

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

yieldreturn語句後的就是一個狀態

Generator 函式的呼叫方法與普通函式一樣,也是在函式名後面加上一對圓括號。不同的是,呼叫 Generator 函式後,該函式並不執行,返回的也不是函式執行結果,而是一個指向內部狀態的指標物件(iterator物件

呼叫該物件的next()方法時,內部指標就從函式頭部或上一次停下來的地方開始執行,直到遇到下一個yield表示式(或return語句)

邏輯如下

  1. 遇到yield表示式,就暫停執行後面的操作,並將緊跟在yield後面的那個表示式的值,作為返回的物件的value屬性值。該表示式為惰性求值

  2. 下一次呼叫next方法時,再繼續往下執行,直到遇到下一個yield

    表示式。

  3. 如果沒有再遇到新的yield表示式,就一直執行到函式結束,直到return語句為止,並將return語句後面的表示式的值,作為返回的物件的value屬性值。

  4. 如果該函式沒有return語句,則返回的物件的value屬性值為undefined。

  • 換言之,Generator 函式是分段執行的,yield表示式是暫停執行的標記,而next方法可以恢復執行。

與iterator介面的關係

Generator 函式就是遍歷器生成函式,因此可以把 Generator 賦值給物件的Symbol.iterator屬性,從而使得該物件具有 Iterator 介面

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]

Generator 函式執行後,返回一個遍歷器物件。該物件本身也具有Symbol.iterator屬性,執行後返回自身。

function* gen(){
  // some code
}

var g = gen();

g[Symbol.iterator]() === g
// true

next方法的引數

yield表示式本身沒有返回值,或者說總是返回undefined。next方法可以帶一個引數,該引數就會被當作上一個yield表示式的返回值。

function* f() {
    var te = yield 1 // yield的預設返回值為undefined
    console.log(te)
    yield 2
    yield 3
  }
  
  var g = f();
  
  console.log(g.next()) // { value: 0, done: false }
  console.log(g.next(2)) // 2,{ value: 1, done: false }

例如這裡的te作為第一次yield的返回值是undefined,但是第二次呼叫的時候傳入了引數2,就會把第二次呼叫next傳入的引數作為第一次的結果返回,所以這裡te的值為2

for...of...迴圈

for...of迴圈可以自動遍歷 Generator 函式執行時生成的Iterator物件,且此時不再需要呼叫next方法

  • 一旦next方法的返回物件的done屬性為true,for...of迴圈就會中止,且不包含該返回物件
function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}

為物件加上遍歷介面

function* objectEntries(obj) {
  let propKeys = Reflect.ownKeys(obj); // 獲取所有鍵

  for (let propKey of propKeys) {
    yield [propKey, obj[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

for (let [key, value] of objectEntries(jane)) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe

或者直接加到原型鏈上面

function* objectEntries() {
  let propKeys = Object.keys(this);

  for (let propKey of propKeys) {
    yield [propKey, this[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

jane[Symbol.iterator] = objectEntries; // 當一個物件使用for...of 迴圈時,會自動尋找物件的jane[Symbol.iterator]並且執行

for (let [key, value] of jane) {
  console.log(`${key}: ${value}`);
}
  • 擴充套件運算子(...)、解構賦值和Array.from方法內部呼叫的,都是遍歷器介面
function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}

// 擴充套件運算子
[...numbers()] // [1, 2]

// Array.from 方法
Array.from(numbers()) // [1, 2]

// 解構賦值
let [x, y] = numbers();
x // 1
y // 2

// for...of 迴圈
for (let n of numbers()) {
  console.log(n)
}
// 1
// 2

throw()方法

  1. 遍歷器內部的try...catch...配合只能捕獲遍歷器已經至少執行過一次next的遍歷器內部的錯誤,並且只會執行一次,在捕獲錯誤的同時也會執行一次next

  2. 遍歷器物件的throw方法和全域性的throw命令互不干擾

  3. 如果 Generator 函式內部沒有部署try...catch程式碼塊,那麼throw方法丟擲的錯誤,將被外部try...catch程式碼塊捕獲。

  4. 如果 Generator 函式內部和外部,都沒有部署try...catch程式碼塊,那麼程式將報錯,直接中斷執行

  5. Generator 函式體外丟擲的錯誤,可以在函式體內捕獲;反過來,Generator 函式體內丟擲的錯誤,也可以被函式體外的catch捕獲。

  6. 一旦 Generator 執行過程中丟擲錯誤,且沒有被內部捕獲,就不會再執行下去了。如果此後還呼叫next方法,將返回一個value屬性等於undefined、done屬性等於true的物件,即 JavaScript 引擎認為這個 Generator 已經執行結束了。

return()方法

  1. 使用return方法終結遍歷 Generator 函式,返回值的value屬性就是return()方法的引數foo,返回值的done屬性為true

  2. 如果 Generator 函式內部有try...finally程式碼塊,且正在執行try程式碼塊,那麼return()方法會導致立刻進入finally程式碼塊,執行完以後,整個函式才會結束。

yield* 表示式

  • 用來在一個 Generator 函式裡面執行另一個 Generator 函式。如果執行另一個 Generator 函式的時候不加*號就會返回該yield命令後的遍歷器物件
  • yield*後面的 Generator 函式(沒有return語句時),等同於在 Generator 函式內部,部署一個for...of迴圈。
  • 如果被代理的 Generator 函式有return語句,那麼就可以向代理它的 Generator 函式返回資料。
function* inner() {
  yield 'hello!';
}

function* outer1() {
  yield 'open';
  yield inner();
  yield 'close';
}

var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一個遍歷器物件
gen.next().value // "close"

function* outer2() {
  yield 'open'
  yield* inner()
  yield 'close'
}

var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"

作為物件的屬性

let obj = {
  * myGeneratorMethod() {
    ···
  }
};