1. 程式人生 > >ES6 Generator函式之基本用法(2)

ES6 Generator函式之基本用法(2)

Generator函式之基本用法(2)

上一篇文章中總結了Generator函式基本概念:
yield表示式,與Iterator介面、for…of迴圈的關係,next方法,throw方法,return方法等內容。
這篇文章接著上一篇文章繼續總結Generator函式的基本用法

(1)yield*表示式

直接在Generator函式內部呼叫另一個Generator函式,預設情況下是沒有效果的:

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

function* bar() {
  yield 'x';
  foo();
  yield
'y'; } for (let v of bar()){ console.log(v); } // "x" // "y"

如果使用yield命令,則會返回一個遍歷器物件:

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

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

    for (let v of bar()){
        console.log(v);
    }
    // "x"
//一個遍歷器物件 foo // "y"
1.yield*語句用來在一個Generator函式內部執行另一個Generator函式
   function* foo() {
        yield 'a';
        yield 'b';
    }

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

    for (let v of bar()){
        console.log(v);
    }
    // "x"
    // "a"
// "b" // "y"

這時候我們稱bar為代理者,foo為被代理者。

2.利用yield*等同於在代理Generator函式內部部署一個for…of迴圈
(前提是,被代理的Generator函式沒有return語句)

以下的程式碼與上面的yield*寫法是等效的:

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

    function* bar() {
        yield 'x';
        for (let item of foo()) {
            console.log(item)
        }
        yield 'y';
    }

    for (let v of bar()) {
        console.log(v);
    }
    // "x"
    // "a"
    // "b"
    // "y"
3.被代理的Generator函式具有return語句,可以向代理它的Generator函式返回值
  function* foo() {
        yield 'a';
        yield 'b';
        return "foo"
    }

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

    for (let v of bar()) {
        console.log(v);
    }
    // "x"
    // "a"
    // "b"
    // "foo"
    // "y"

另外一個例子:

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

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

   let a=[...bar()];
    console.log(a);
    //["x","a","b","foo","y"]
4.yield*後面跟著帶有Iterator介面的資料結構
   //字串
    function* foo() {
        yield* "qwe"
    }
    for (let v of foo()) {
        console.log(v);
    }
    // q
    // w
    // e

    
    //陣列
    function* bar() {
        yield* [1, 2, 3]
    }
    for (let v of bar()) {
        console.log(v);
    }
    // 1
    // 2
    // 3

    
    //Set結構
    function* foo1() {
        yield* new Set(["q", "w", "e"])
    }
    for (let v of foo1()) {
        console.log(v);
    }
    // q
    // w
    // e

    
    //Map結構
    function* bar1() {
        yield* new Map([["q", 1], ["w", 2], ["e", 3]])
    }
    for (let v of bar1()) {
        console.log(v);
    }
    // ["q",1]
    // ["w",2]
    // ["e",3]
5.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

(2)Generator函式作為物件屬性

如果一個物件的屬性是Generator函式,可以寫成下面的形式:

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

//等同於
let obj = {
  myGeneratorMethod: function* () {
    // ···
  }
};

(3)Generator函式的this

Generator函式總是返回一個遍歷器物件,ES6規定這個遍歷器物件是Generator函式的例項:

   function* g() {}
    let obj = g();
    console.log(obj instanceof g)
    // true

Generator函式返回的遍歷器會繼承Generator函式的prototype物件上的方法:

    function* g() {}

    g.prototype.hello = function () {
        return 'hi!';
    };

    let obj = g();
    console.log(obj.hello()) // 'hi!'

注意,Generator函式返回的是遍歷器物件,而不是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()
// TypeError: F is not a constructor

那麼,有沒有辦法讓 Generator 函式返回一個正常的物件例項,既可以用next方法,又可以獲得正常的this?

下面是一個變通方法。首先,生成一個空物件,使用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

上面程式碼中,首先是F內部的this物件繫結obj物件,然後呼叫它,返回一個 Iterator 物件。這個物件執行三次next方法(因為F內部有兩個yield表示式),完成 F 內部所有程式碼的執行。這時,所有內部屬性都繫結在obj物件上了,因此obj物件也就成了F的例項。

上面程式碼中,執行的是遍歷器物件f,但是生成的物件例項是obj,有沒有辦法將這兩個物件統一呢?

一個辦法就是將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

(4)應用

1.處理非同步操作,改寫回調函式

非同步操作的同步化表達。處理非同步操作時,在寫法上可以做到與同步寫法類似。

2.控制流管理
3.部署Iterator介面

利用Generator函式可以在任意物件上部署Iterator介面

function* iterEntries(obj) {
  let keys = Object.keys(obj);
  for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
  }
}

let myObj = { foo: 3, bar: 7 };

for (let [key, value] of iterEntries(myObj)) {
  console.log(key, value);
}

// foo 3
// bar 7
4.作為資料結構

參考文獻:《ECMAScript 6 入門》阮一峰