混子前端所知道關於ES6的Generator

前面文章介紹了Iterator,本文繼續介紹Generator。
Generator函數是ES6提供的一種異步編程解決方案,語法行爲與傳統函數徹底不一樣。
---本文摘選阮一峯《ECMAScript 6 標準入門》

基本概念

Generator函數從語法上是一個狀態機,封裝了多個內部狀態,執行Generator函數會返回一個遍歷器對象,返回的遍歷器對象,能夠依次遍歷Generator函數內部的每個狀態。
編程

Generator函數從形式上是一個普通函數,可是有兩個特徵:
  1. function 關鍵字與函數名之間有一個星號
  2. 函數體內部使用 yield 語句,定義不一樣的內部狀態

JS code:

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函數 helloWorldGenerator有三個狀態:hello,world 和 return 語句,這裏調用 Generator 函數後並不執行,而是返回一個指向內部狀態的指針對象,開發人員必須手動調用遍歷器對象的next方法,使指針移動到下一個狀態,即 Generator 函數是分段執行的,yield語句是暫停執行的標記,而 next 方法能夠恢復執行。
bash

next 方法返回一個對象,對象value是當前 yield 語句的值,done表示遍歷的狀態 ( false: 沒結束 ) app


yield 語句

Generator函數返回的遍歷器對象,只有調用 next 方法纔會遍歷下一個內部狀態,因此提供了 yield 暫停執行函數。異步

遍歷器對象的 next 方法運行邏輯以下:異步編程

  1. 遇到 yield 語句暫停執行後面的操做,並將在 yield 後面那個表達式的值,做爲返回對象的 value 屬性值
  2. 下次調用 next 方法時,再繼續往下執行,直到遇到下一個 yield 語句
  3. 若是沒有遇到新的 yield 語句,就一直運行函數結束 到 return 語句爲止,將 return 的值做爲返回的對象 value 的屬性值
  4. 若是該函數沒有 return 語句,則返回的對象的 value 屬性值爲 undefined 

yield語句與return的區別:函數

  1. return 語句不具有位置記憶功能,即函數遇到 yield 函數暫停執行,下一次在從該位置繼續執行。
  2. 一個函數裏只能執行一次 return 語句,但能夠執行屢次 yield 語句

Generator函數能夠不用 yield 語句,這時就變成了一個單純的暫緩執行函數,來看代碼:ui

JS code:

function* f() {
    console.log('執行了!')
}

var generator = f();    

setTimeout(function () {
    generator.next()
}, 2000);複製代碼

注意:以上代碼直接調用函數f(); 不會執行console語句,無論有沒有 yield 語句都要執行 next 方法才能調用 Generator 函數,另外 yield 函數也不能存在普通函數中,不然會直接拋出錯誤,還須要注意的是在 Generator 函數的 forEach / map 等循環語句中,也不能夠調用 yield 方法,不然會產生語法錯誤,另外,yield 語句若是在表達式中,必須用括號擴起來。this

與Iterator接口的關係

任意一個對象的 Symbol.iterator 方法,等於該對象的遍歷器生成函數,調用該函數會返回該對象的一個遍歷器對象。但因爲 Generator 函數就是遍歷器生成函數,所以能夠把 Generator 賦值給對象的 Symbol.iterator 屬性,從而使得該對象具備 Iterator 接口,來看代碼:spa

JS code:

var myIterable = {};

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

[...myIterable]    // [1,2,3]
複製代碼

說明:上面代碼 Generator函數賦值給 Symbol.iterator 屬性,所以 myIterable 對象具備了Iterator接口,能夠被 ... 運算符遍歷了。prototype


next方法的參數

next 方法能夠帶一個參數,該參數會做爲上一個 yield 語句的返回值,來看代碼:

JS code:

function* foo(x) {
    var y = 2 * (yield (x + 1));
    var z = yield (y / 3);
    return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
複製代碼

說明:

第一個 a.next() 沒有任何爭議;第二個 a.next() 爲何拋出NaN,由於next 沒有攜帶參數默認 undefined,致使 undefined / 3 後變成NaN;同理第三個 a.next() 中return(x+y+z),內部運做實際上是 5 + NaN + undefined,因此 value 也是 NaN;

下面來看一下第二次 b.next() value 輸出8,是傳遞了12做爲上一次yeild的值,y就變成了2*12=24,那24/3=8,因此value 爲 8;來看一下第三次b.next(),此時 x 爲 5 毫無爭議,z是傳遞進去的13,因爲上一次next傳入的值爲12,y=12*2=24,因此return(5+24+13)輸出42;

注意:next 方法的參數表示上一個 yield 語句的返回值,因此第一次使用 next 不能帶有參數


Generator.prototype.throw()

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

JS code:

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

var i = g();
i.next();

try {
    i.throw('a');
    i.throw('b');
} catch (e) {
    console.log('外部捕獲', e);
}
// 內部捕獲 a
// 外部捕獲 b

複製代碼

說明:上面代碼遍歷器 i 連續拋出兩個錯誤,第一個錯誤被 Generator 函數題內 catch 捕獲,i 的第二個錯誤因爲 Generator 函數內部的

 
catch  語句已經執行過了,不會再捕捉到這個錯誤了, 因此這個錯誤被拋出 Generator 函數體,被函數體外的  catch  語句捕獲。


Generator.prototype.return()

Generator函數返回的遍歷器對象 return 方法,能夠返回給定的值,終結遍歷Generator函數。

JS code:

function* gen() {
    yield 1;
    yield 2;
    yield 3;
}

var g = gen();

g.next()         // { value: 1, done: false }
g.return('foo')  // { value: "foo", done: true }
g.next()         // { value: undefined, done: true }
複製代碼

說明:遍歷器對象 調用 return 方法後,返回值 value 屬性就是 return 方法的參數 foo,同時,Generator函數的遍歷就終止了,done 值置爲true。


yield*語句

若是在Generater函數內部,調用另外一個Generator函數,默認狀況下是沒有效果的,來看代碼:

JS code:

function* foo() {
    yield 'a';
    yield 'b';
}

function* bar() {
    yield 'x';    
    foo();
    yield 'y';
}

function* gen() {
    yield 'x';    
    yield* foo();
    yield 'y'
}

for (let v of bar()){
    console.log(v);
}// 'x'
// 'y'
for (let v of gen()){
    console.log(v);
}
// 'x'
// 'a'
// 'b'
// 'y'複製代碼

說明:上面代碼在 bar 裏面調用 foo,是不會有效果的,要用到 yield* 語句,用來在一個Generator 函數裏面執行另外一個 Generator 函數。


Generator函數的this

Generator 函數老是返回一個遍歷器,ES6規定這個遍歷器是 Generator 函數的實例,也繼承了Generator 函數的 prototype 對象上的方法。
但若是把 Generator 函數看成普通的構造函數,並不會生效,同時 Generator 函數也不能跟new 命令一塊兒用,來看代碼:

JS code:

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
複製代碼

說明:能夠更換執行者 F 內部的 this 對象綁定對象,而後調用它,返回一個Iterator對象


那文章的最後仍是老規矩,歡迎你們點贊和糾錯。

祝各位週一愉快!

相關文章
相關標籤/搜索