es6 Generator

簡介

  • 把它理解成一個狀態機,封裝了多個內部狀態,執行Generator函數會返回一個遍歷器對象,可以依次遍歷Generator函數內部的每一個狀態。
  • 定義時function與函數名之間有一個星號
  • 內部使用yield(產出)語句,定義不同的內部狀態
  • 調用遍歷器對象的next方法,使指針移向下一個狀態

調用Generator函數后,該函數并不執行,而是返回一個指向內部狀態的指針對象,每次調用next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一個yield語句(或return語句)為止。Generator函數是分段執行的,yield語句是暫停執行的標記,而next方法可以恢復執行

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 },執行完成done變成true

hw.next()
// { value: undefined, done: true }

yield語句

  • 遍歷器對象的next遇到yield語句,就暫停執行后面的操作,并將緊跟在yield后面的那個表達式的值,作為返回的對象的value屬性值
  • 下一次調用next方法時,再繼續往下執行,直到遇到下一個yield語句
  • 如果沒有再遇到新的yield語句,就一直運行到函數結束,直到return語句為止,并將return語句后面的表達式的值,作為返回的對象的value屬性值,若無則返回undefined
  • yield語句如果用在一個表達式之中,必須放在圓括號里面,作函數參數或賦值表達式的右邊除外

yield與return區別在于每次遇到yield,函數暫停執行,下一次再從該位置繼續向后執行,而return不具備位置記憶的功能。一個函數里面,只能執行一次(或者說一個)return語句,但是可以執行多次(或者說多個)yield語句,所以Generator函數可以返回一系列的值,即生成器之意

yield句本身沒有返回值,或者說總是返回undefined。next方法可以帶一個參數,該參數就會被當作上一個yield語句的返回值。第一次使用next方法時,不能帶有參數,V8引擎直接忽略第一次使用next方法時的參數,只有從第二次使用next方法開始,參數才是有效的。從語義上講,第一個next方法用來啟動遍歷器對象,所以不用帶有參數。

function* f() {
  for(var i=0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

通過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

與Iterator接口的關系

  • 執行Generator函數后返回Iterator對象

for...of循環、擴展運算符(...)、解構賦值和Array.from方法內部調用的,都是使用遍歷器接口,即可以自動遍歷Generator函數

擴展運算符(...)用于取出參數對象的所有可遍歷屬性,拷貝到當前對象之中。

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

擴展運算符可以用于合并兩個對象。

let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

Generator.prototype.return()

  • 遍歷器對象的return方法,可以返回給定的值,并且終結遍歷Generator函數
function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next()        // { value: 1, done: false }
g.return("foo") // { value: "foo", done: true },return不提供參數,則返回值的vaule屬性為undefined
g.next()        // { value: undefined, done: true }

yield*語句

  • 用來在一個Generator函數里面執行另一個Generator函數
function* foo() {
  yield 'a';
  yield 'b';
}

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*語句等同于在Generator函數內部,部署一個for...of循環 ,如果yield*后面跟著一個數組,由于數組原生支持遍歷器,因此就會遍歷數組成員,還有字符串之類有Iterator接口的數據結構

function* gen(){
  yield* ["a", "b", "c"];
}

gen().next() // { value:"a", done:false }

// 下面是二叉樹的構造函數,
// 三個參數分別是左樹、當前節點和右樹
function Tree(left, label, right) {
  this.left = left;
  this.label = label;
  this.right = right;
}

// 下面是中序(inorder)遍歷函數。
// 由于返回的是一個遍歷器,所以要用generator函數。
// 函數體內采用遞歸算法,所以左樹和右樹要用yield*遍歷
function* inorder(t) {
  if (t) {
    yield* inorder(t.left);
   ?????        yield t.label;
    yield* inorder(t.right);
  }
}

// 下面生成二叉樹
function make(array) {
  // 判斷是否為葉節點
  if (array.length == 1) return new Tree(null, array[0], null);
  return new Tree(make(array[0]), array[1], make(array[2]));
}
let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);

// 遍歷二叉樹
var result = [];
for (let node of inorder(tree)) {
  result.push(node);
}

result
// ['a', 'b', 'c', 'd', 'e', 'f', 'g']

Generator的一些應用

異步轉同步

可以把異步操作寫在yield語句里面,等到調用next方法時再往后執行

function* main() {
    var result = yield request("http://some.url");
    var resp = JSON.parse(result);
    console.log(resp.value);
}
function request(url) {
    makeAjaxCall(url, function(response) {
        it.next(response);   //因為yield語句構成的表達式,本身是沒有值的,必須加上response參數
    });
}
var it = main();
it.next();

流程控制

多個任務需要并列執行時,可以采用數組的寫法

function* parallelDownloads() {
  let [text1,text2] = yield [
    taskA(),
    taskB()
  ];
  console.log(text1, text2);
}

部署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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 簡介 基本概念 Generator函數是ES6提供的一種異步編程解決方案,語法行為與傳統函數完全不同。本章詳細介紹...
    呼呼哥閱讀 1,090評論 0 4
  • 本文作者就是我,簡書的microkof。如果您覺得本文對您的工作有意義,產生了不可估量的價值,那么請您不吝打賞我,...
    microkof閱讀 23,773評論 16 78
  • 在此處先列下本篇文章的主要內容 簡介 next方法的參數 for...of循環 Generator.prototy...
    醉生夢死閱讀 1,463評論 3 8
  • Generator函數是協程在ES6的實現,最大的特點就是可以交出函數的執行權(即暫停執行)。Generator函...
    會飛的豬l閱讀 183評論 0 1
  • 新的一周開始了,感恩應妲老師的一大早的能量早餐,今天早餐開始感恩食物,嚼著食物,想著謝謝你,感覺今天的早餐特別香,...
    幸福的小兔豬閱讀 127評論 0 0