Generator 函數學習筆記

// 使用 function* 定義一個 generator 函數
function* helloWorldGenerator() {
  yield 'hello';  // yield 關鍵字做爲暫停的點
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();  // 執行 generator 函數,返回一個遍歷器對象。而這時,這個函數內的代碼並不會執行。
// 調用遍歷器對象的 next 方法,執行函數內的代碼,執行到下一個 yield 的位置,並暫停執行
hw.next()
// { value: 'hello', done: false }  value 是 yield 後面跟的表達式的值,done 是 genertator 函數結束狀態

// 再次調用 next,執行到下一個 yield 位置
hw.next()
// { value: 'world', done: false }

// 執行結束,value 值爲 return 的值,沒有 return 則爲 undefined(函數沒 return 返回 undefined),done 變爲 true
hw.next()
// { value: 'ending', done: true }

// 還能夠無限次調用 next,可是都返回相同的對象
hw.next()
// { value: undefined, done: true }

yield 不能用在普通函數中:數組

var flat = function* (a) {
  // forEach 方法是個普通函數,在裏面使用了 yield 會報錯。解決方法是改成 for 循環
  a.forEach(function (item) {
    if (typeof item !== 'number') {
      yield* flat(item);
    } else {
      yield item;
    }
  }
};

yield語句若是用在一個表達式之中,必須放在圓括號裏面。函數

console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError

console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK

 

next方法的參數this

function* foo(x) {
  var y = 2 * (yield (x + 1));  // yield 語句在表達式中,須要將 yield 語句括起來,不然報錯
  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 }  調用第一次 next 開始執行,獲得第一個 yield 的返回值 6。因爲 next 參數爲上一個 yield 語句的值,因此第一個 next 傳入參數沒有意義
b.next(12) // { value:8, done:false }  調用 next 方法時注入了數據,做爲上一個 yield 語句的值,獲得 var y = 2 * 12
b.next(13) // { value:42, done:true }  獲得 var z = 13

for...of循環spa

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

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

for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5  這裏須要注意,一旦方法的返回對象的屬性爲,循環就會停止,且不包含該返回對象,因此上面代碼的語句返回的6,不包括在循環之中。nextdonetruefor...ofreturnfor...of

 原生的JavaScript對象沒有遍歷接口,沒法使用for...of循環,經過Generator函數爲它加上這個接口,就能夠用了。code

// 第一種方法
function* objectEntries(obj) {
  let propKeys = Reflect.ownKeys(obj);

  for (let propKey of propKeys) {
    yield [propKey, obj[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

for (let [key, value] of objectEntries(jane)) {
  console.log(`${key}: ${value}`);
}

// 第二種方法
function* objectEntries() {
  let propKeys = Object.keys(this);

  for (let propKey of propKeys) {
    yield [propKey, this[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

jane[Symbol.iterator] = objectEntries;

for (let [key, value] of jane) {
  console.log(`${key}: ${value}`);
}

 

Generator.prototype.throw()對象

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

var g = function* () {
// 使用 try...catch... 進行異常捕獲
try { yield; } catch (e) { console.log('內部捕獲', e); } }; var i = g(); i.next(); try { i.throw('a'); // 這裏使用 throw 方法拋出的錯誤,會由 generator 函數內的 catch 處理 i.throw('b'); // generator 內的 catch 已經執行過了,就不會再被 generator 的 catch 捕獲了,由外部的 catch 捕獲 } catch (e) { console.log('外部捕獲', e); } // 內部捕獲 a // 外部捕獲 b

若是Generator函數內部沒有部署try...catch代碼塊,那麼throw方法拋出的錯誤,將被外部try...catch代碼塊捕獲。接口

若是Generator函數內部和外部,都沒有部署try...catch代碼塊,那麼程序將報錯,直接中斷執行。ip

throw方法被捕獲之後,會附帶執行下一條yield語句。也就是說,會附帶執行一次next方法。

var gen = function* gen(){
  try {
    yield console.log('a');
  } catch (e) {
    // ...
  }
  yield console.log('b');  // throw 方法會附帶執行 next,從而執行到這個 yield 位置
  yield console.log('c');
}

var g = gen();
g.next() // a
g.throw() // b
g.next() // c

 

Generator.prototype.return()

Generator函數返回的遍歷器對象,還有一個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 }  value 值變成了 return 的參數
g.next()        // { value: undefined, done: true }  return 方法 致使 generator 函數結束,因此 value 爲 undefined

 

yield*語句

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

function* bar() {
  yield 'x';
  // foo();  若是隻是單純的執行 foo() 函數,只是獲得一個遍歷器對象,並不會產生什麼效果。
  yield* foo();  // 使用了 yield* 語句,在遍歷的時候纔會遍歷這個 generator 函數內部的 generator 函數。
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"
function* gen(){
  yield* ["a", "b", "c"];  // 數組、字符串等,帶有 iterator 接口的,均可以被 yield* 遍歷
}

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

 

Generator與狀態機

var clock = function*() {
  while (true) {
    console.log('Tick!');  // 執行狀態1代碼
    yield;
    console.log('Tock!');  // 執行狀態2代碼
    yield;
  }
};

每次調用 next() 就能夠在兩種狀態間切換執行,而不須要使用一個布爾變量來作判斷

相關文章
相關標籤/搜索