簡介
- 把它理解成一個狀態機,封裝了多個內部狀態,執行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