Generator知識點雜燴

Generator

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

執行 Generator 函數會返回一個遍歷器對象,也就是說,Generator 函數除了狀態機,仍是一個遍歷器對象生成函數。返回的遍歷器對象,能夠依次遍歷 Generator 函數內部的每個狀態。數據結構

  • Generator 函數有兩個特徵:函數

    • function關鍵字與函數名之間有一個星號
    • 函數體內部使用yield表達式,定義不一樣的內部狀態(yield在英語裏的意思就是「產出」)
    function* helloWorldGenerator() {
      yield 'hello';
      yield 'world';
      return 'ending';
      }
    
      var hw = helloWorldGenerator(); 
      hw.next()
      // { value: 'hello', done: false } value屬性就是當前yield表達式的值,done屬性爲false,表示遍歷尚未結束。
    
      hw.next()
      // { value: 'world', done: false }
    
      hw.next()
      // { value: 'ending', done: true }
    
      hw.next()
      // { value: undefined, done: true } done屬性爲true,表示遍歷已經結束。   
    複製代碼

上面代碼定義了一個 Generator 函數helloWorldGenerator,它內部有兩個yield表達式(helloworld),即該函數有三個狀態:hello,world 和 return 語句(結束執行)。
調用方法與普通函數同樣,可是調用,函數並不執行,返回一個指向內部狀態的指針對象,也就是遍歷器對象。 必須調用遍歷器對象的next方法,使得指針移向下一個狀態。內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield表達式(或return語句)爲止。
換言之,Generator 函數是分段執行的,yield表達式是暫停執行的標記,而next方法能夠恢復執行。this

yield 表達式

因爲 Generator 函數返回的遍歷器對象,只有調用next方法纔會遍歷下一個內部狀態,因此其實提供了一種能夠暫停執行的函數。yield表達式就是暫停標誌。所以等於爲 JavaScript 提供了手動的「惰性求值」(Lazy Evaluation)的語法功能。
yield表達式只能用在 Generator 函數裏面,用在其餘地方都會報錯。lua

next 方法的參數

yield表達式自己沒有返回值,或者說老是返回undefinednext方法能夠帶一個參數,該參數就會被看成上一個yield表達式的返回值。spa

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

上面代碼中,第二次運行next方法的時候不帶參數,致使 y 的值等於2 * undefined(即NaN),除以 3 之後仍是NaN,所以返回對象的value屬性也等於NaN。第三次運行Next方法的時候不帶參數,因此z等於undefined,返回對象的value屬性等於5 + NaN + undefined,即NaNprototype

若是向next方法提供參數,返回結果就徹底不同了。上面代碼第一次調用b的next方法時,返回x+1的值6;第二次調用next方法,將上一次yield表達式的值設爲12,所以y等於24,返回y / 3的值8;第三次調用next方法,將上一次yield表達式的值設爲13,所以z等於13,這時x等於5y等於24,因此return語句的值等於42指針

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

next方法的參數,也能夠向Generator 函數內部輸入值對象

function* dataConsumer() {
    console.log('Started');
    console.log(`1. ${yield}`);
    console.log(`2. ${yield}`);
    return 'result';
    }

    let genObj = dataConsumer();
    genObj.next();
    // Started
    genObj.next('a')
    // 1. a
    genObj.next('b')
    // 2. b  
複製代碼

for...of 循環

for...of循環能夠自動遍歷 Generator 函數時生成的Iterator對象,且此時再也不須要調用next方法。

function* foo() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
    return 6; //return語句返回的,不包括在for...of循環之中
    }

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

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

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

Generator.prototype.throw()

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

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

一旦執行了catch,捕捉了錯誤,Generator 函數就已經結束了,再也不執行下去了。

Generator.prototype.return()

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

next()、throw()、return() 的共同點

本質上是同一件事,能夠放在一塊兒理解。它們的做用都是讓 Generator 函數恢復執行,而且使用不一樣的語句替換yield表達式。
next()是將yield表達式替換成一個值。

const g = function* (x, y) {
    let result = yield x + y;
    return result;
    };

    const gen = g(1, 2);
    gen.next(); // Object {value: 3, done: false}

    gen.next(1); // Object {value: 1, done: true}
    // 至關於將 let result = yield x + y
    // 替換成 let result = 1;  
複製代碼

throw()是將yield表達式替換成一個throw語句。

gen.throw(new Error('出錯了')); // Uncaught Error: 出錯了
    // 至關於將 let result = yield x + y
    // 替換成 let result = throw(new Error('出錯了'));  
複製代碼

return()是將yield表達式替換成一個return語句。

gen.return(2); // Object {value: 2, done: true}
    // 至關於將 let result = yield x + y
    // 替換成 let result = return 2;  
複製代碼

yield* 表達式

若是在 Generator 函數內部,調用另外一個 Generator 函數,默認狀況下是沒有效果的。yield*表達式,用來在一個 Generator 函數裏面執行另外一個 Generator 函數。

function* foo() {
    yield 'a';
    yield 'b';
    }
    //普通方法調用foo() ==========================
    function* bar() {
    yield 'x';
    foo(); 
    yield 'y';
    }
    for (let v of bar()){
    console.log(v);
    }
    // "x"
    // "y"  
    //上面foo()的調用是沒有效果的

    //yield*表達式調用 =================================
    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);
    }
    // "x"
    // "a"
    // "b"
    // "y"
複製代碼

從語法角度看,若是yield表達式後面跟的是一個遍歷器對象,須要在yield表達式後面加上星號,代表它返回的是一個遍歷器對象。這被稱爲yield*表達式。

yield*後面的 Generator 函數(沒有return語句時),等同於在 Generator 函數內部,部署一個for...of循環。

function* concat(iter1, iter2) {
    yield* iter1;
    yield* iter2;
    }

    // 等同於

    function* concat(iter1, iter2) {
    for (var value of iter1) {
        yield value;
    }
    for (var value of iter2) {
        yield value;
    }
    }  
複製代碼

上面代碼說明,yield*後面的 Generator 函數(沒有return語句時),不過是for...of的一種簡寫形式,徹底能夠用後者替代前者。反之,在有return語句時,則須要用var value = yield* iterator的形式獲取return語句的值。

實際上,任何數據結構只要有 Iterator 接口,就能夠被yield*遍歷。

yield*命令能夠很方便地取出嵌套數組的全部成員。

function* iterTree(tree) {
    if (Array.isArray(tree)) {
        for(let i=0; i < tree.length; i++) {
        yield* iterTree(tree[i]);
        }
    } else {
        yield tree;
    }
    }

    const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];

    for(let x of iterTree(tree)) {
    console.log(x);
    }
    // a
    // b
    // c
    // d
    // e 
複製代碼

Generator 函數的this

Generator 函數g返回的遍歷器obj,是g的實例,並且繼承了g.prototype。可是,若是把g看成普通的構造函數,並不會生效,由於g返回的老是遍歷器對象,而不是this對象,也不能跟new命令一塊兒用,會報錯。

下面是一個變通方法。首先,生成一個空對象,使用call方法綁定 Generator 函數內部的this。這樣,構造函數調用之後,這個空對象就是 Generator 函數的實例對象了。

function* F() {
    this.a = 1;
    yield this.b = 2;
    yield this.c = 3;
    }
    var obj = {};
    var f = F.call(obj);

    f.next();  // Object {value: 2, done: false}
    f.next();  // Object {value: 3, done: false}
    f.next();  // Object {value: undefined, done: true}

    obj.a // 1
    obj.b // 2
    obj.c // 3  
複製代碼

還有一個辦法就是將obj換成F.prototype

function* F() {
    this.a = 1;
    yield this.b = 2;
    yield this.c = 3;
    }
    var f = F.call(F.prototype);

    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改爲構造函數,就能夠對它執行new命令了。

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