es6之Generator函數

什麼是Generator函數

Generator函數,能夠理解爲一種狀態機,封裝了多個內部狀態。是一個遍歷器生成器,返回遍歷器對象(即Generator 函數的內部指針)。bash

Generator函數和普通函數區別:函數

  • 一、函數function和函數名稱中間有個星號
  • 二、調用 Generator 函數後,該函數並不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象
  • 三、函數內部使用yield表達式定義內部狀態,普通函數不能使用yield表達式。能夠不用yield表達式,這時就變成了一個單純的暫緩執行函數
  • 四、必須調用遍歷器對象的next方法,使得指針移向下一個狀態
function* foo(){
    console.log('hello');
}
var bar = foo();

bar.next();
bar.next();

//'hello'
複製代碼

一個普通函數中使用yield表達式,結果產生一個句法錯誤ui

(function(){
    yield 1;
})(); // 報錯

(function*(){
    yield 1;
})();//ok
複製代碼
function* foo() {
    console.log('start');
    yield 1;
    console.log('middle');
    yield 2;
    console.log('end');
    return 3;
}
var bar = foo();
console.log(bar.next());
console.log(bar.next());
console.log(bar.next());
console.log(bar.next());

//start
//{ value: 1, done: false }
//middle
//{ value: 2, done: false }
//end
//{ value: 3, done: true }
//{ value: undefined, done: true }
複製代碼

Generator函數的運行流程:this

  • 第 1 次調用next,輸出start,遇到第一個yield,返回{ value: 1, done: false }
  • 第 2 次調用next,從上一次yield停下來的地方,直到下一個yield表達式。輸出middle,遇到第二個yield,返回{ value: 2, done: false }
  • 第 3 次調用next,從上一次yield停下來的地方,遇到return。輸出end,返回{ value: 3, done: true }
  • 第 4 次調用next,返回{ value: 3, done: true }。若是還有next調用都返回這個結果

yield 和return

區別在於每次遇到yield,函數暫停執行,下一次再從該位置繼續向後執行,而return語句不具有位置記憶的功能。spa

yield表達式自己沒有返回值,或者說老是返回undefined。yield表達式若是用在另外一個表達式之中,必須放在圓括號裏面指針

function* foo() {
    //在另外一個表達式中,yield表達式必須加上圓括號
    console.log( 'hello' +  (yield 'world'));
}
var bar = foo();
console.log(bar.next());
console.log(bar.next());

//{ value: 'world', done: false }
//helloundefined
//{ value: undefined, done: true }
複製代碼

yield表達式用做函數參數或放在賦值表達式的右邊,能夠不加括號code

function fun(a){
    console.log('a:'+ a);
}
function* foo() {
    fun(yield 'hello', yield 'world'); //函數參數
    let value = yield; //表達式右側
}
var bar = foo();
console.log(bar.next()); 
console.log(bar.next());
console.log(bar.next());
console.log(bar.next());

//{ value: 'hello', done: false }
//{ value: 'world', done: false }
//a:undefined
//{ value: undefined, done: false }
//{ value: undefined, done: true }
複製代碼

與Iterator接口

任意一個對象的Symbol.iterator方法等於該對象的遍歷器生成函數,調用該函數會返回該對象的一個遍歷器對象。對象

Generator 函數執行後,返回一個遍歷器對象。該對象自己也具備Symbol.iterator屬性,執行後返回自身。接口

function* foo() {
    yield 1;
}
var bar = foo();
bar === bar[Symbol.iterator]() //true
複製代碼

Generator 函數賦值給Symbol.iterator屬性,從而使得myIterable對象具備了 Iterator 接口,能夠被...運算符遍歷了generator

var myIterable = {};
myIterable[Symbol.iterator] = function* foo() {
    yield 1;
    yield 2;
};
[...myIterable] //[1, 2]
複製代碼

next方法參數

next方法能夠帶一個參數,該參數就會被看成上一個yield表達式的返回值

function* foo() {
    let a = 10;
    let b = yield 10 + a;
    yield b + 10;
}

var bar = foo();

bar.next();    //{ value: 20, done: false }
bar.next(20); //{ value: 30, done: false }
bar.next();   //{ value: undefined, done: true }
複製代碼

第二個next參數20做爲,第一次yield的返回值,全部b = 20 ,因此第二個yield表達式爲值爲30

Generator 函數從暫停狀態到恢復運行,它的上下文狀態(context)是不變的。經過next方法的參數,就有辦法在 Generator 函數開始運行以後,繼續向函數體內部注入值。

for...of循環

for...of循環遍歷Iterator對象,再也不須要調用next方法

function* foo() {
  yield 1;
  yield 2;
  return 3;
}

for (let v of foo()) {
  console.log(v);
}//1 2
複製代碼

須要注意,一旦next方法的返回對象的done屬性爲true,for...of循環就會停止,且不包含該返回對象,因此上面代碼的return語句返回的3,不包括在for...of循環之中

for...of循環、擴展運算符(...)、解構賦值和Array.from方法內部調用的,都是遍歷器接口。這意味着,它們均可以將 Generator 函數返回的 Iterator 對象,做爲參數。

使對象類型數據也能使用for...of 第一種

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

    for (let key of keys) {
        yield [key, this[key]];
    }
}
var bar = { name: 'li', age: 20};
bar[Symbol.iterator] = foo;

for (let value of bar) {
    console.log(value);
}
//[ 'name', 'li' ]
//[ 'age', 20 ]
複製代碼

第二種實現

function* foo(obj) {
    let keys = Object.keys(obj);
    for(let key of keys){
        yield [key, obj[key]];
    }
}
var bar = {name: 'li', age: 20, sex: 'man'};

for (let value of foo(bar)){
    console.log(value);
}
複製代碼

throw方法

generator.throw()拋出錯誤先看內部又沒有捕獲,若是內部沒有捕獲,就外部捕獲。若是都沒有捕獲,那麼程序將報錯,直接中斷執行。

注意區分throw方法和全局的throw

function* foo() {
    try{
        yield 1;
    } catch(error){
        console.log('error');
    }
    yield 2;
}

var bar = foo();

bar.next(); //{ value: 1, done: false }
bar.throw();//{ value: 2, done: false }

throw new Error('xxx');
複製代碼
  • bar.throw方法拋出的錯誤要被內部捕獲,前提是必須至少執行過一次next方法
  • bar.throw方法被捕獲之後,會附帶執行下一條yield表達式,也就是說,會附帶執行一次next方法
  • throw命令與g.throw方法是無關的,二者互不影響
  • 一旦 Generator 執行過程當中拋出錯誤,且沒有被內部捕獲,就不會再執行下去了。若是此後還調用next方法,返回{ value: undefined, done: true }

return方法

若是return方法傳有參數就當作返回對象的value值。日後再調用next方法都返回{ value: undefined, done: true }

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

若是Generator中有try...finaly,調用return()方法後,就開始執行finally代碼塊,不執行try裏面剩下的代碼了,而後等到finally代碼塊執行完,再返回return()方法指定的返回

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

var g = gen();

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

yield*表達式

在 Generator 函數內部,調用另外一個 Generator 函數。

function* foo() {
    yield 1;
    yield* bar();
    yield 2;
}
function* bar() {
    yield 3;
    yield 4;
}

var g = foo();

[...g];//[ 1, 3, 4, 2 ]
複製代碼
相關文章
相關標籤/搜索