前面文章介紹了Iterator,本文繼續介紹Generator。
Generator函數是ES6提供的一種異步編程解決方案,語法行爲與傳統函數徹底不一樣。
---本文摘選阮一峯《ECMAScript 6 標準入門》
Generator函數從語法上是一個狀態機,封裝了多個內部狀態,執行Generator函數會返回一個遍歷器對象,返回的遍歷器對象,能夠依次遍歷Generator函數內部的每個狀態。
編程
JS code:
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 }
hw.next() // { value: undefined, done: true }
複製代碼
說明:Generator函數 helloWorldGenerator有三個狀態:hello,world 和 return 語句,這裏調用 Generator 函數後並不執行,而是返回一個指向內部狀態的指針對象,開發人員必須手動調用遍歷器對象的next方法,使指針移動到下一個狀態,即 Generator 函數是分段執行的,yield語句是暫停執行的標記,而 next 方法能夠恢復執行。
bash
next 方法返回一個對象,對象value是當前 yield 語句的值,done表示遍歷的狀態 ( false: 沒結束 ) app
Generator函數返回的遍歷器對象,只有調用 next 方法纔會遍歷下一個內部狀態,因此提供了 yield 暫停執行函數。異步
遍歷器對象的 next 方法運行邏輯以下:異步編程
yield語句與return的區別:函數
Generator函數能夠不用 yield 語句,這時就變成了一個單純的暫緩執行函數,來看代碼:ui
JS code:
function* f() {
console.log('執行了!')
}
var generator = f();
setTimeout(function () {
generator.next()
}, 2000);複製代碼
注意:以上代碼直接調用函數f(); 不會執行console語句,無論有沒有 yield 語句都要執行 next 方法才能調用 Generator 函數,另外 yield 函數也不能存在普通函數中,不然會直接拋出錯誤,還須要注意的是在 Generator 函數的 forEach / map 等循環語句中,也不能夠調用 yield 方法,不然會產生語法錯誤,另外,yield 語句若是在表達式中,必須用括號擴起來。this
任意一個對象的 Symbol.iterator 方法,等於該對象的遍歷器生成函數,調用該函數會返回該對象的一個遍歷器對象。但因爲 Generator 函數就是遍歷器生成函數,所以能夠把 Generator 賦值給對象的 Symbol.iterator 屬性,從而使得該對象具備 Iterator 接口,來看代碼:spa
JS code:
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1,2,3]
複製代碼
說明:上面代碼 Generator函數賦值給 Symbol.iterator 屬性,所以 myIterable 對象具備了Iterator接口,能夠被 ... 運算符遍歷了。prototype
next 方法能夠帶一個參數,該參數會做爲上一個 yield 語句的返回值,來看代碼:
JS code:
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 }
複製代碼
說明:
第一個 a.next() 沒有任何爭議;第二個 a.next() 爲何拋出NaN,由於next 沒有攜帶參數默認 undefined,致使 undefined / 3 後變成NaN;同理第三個 a.next() 中return(x+y+z),內部運做實際上是 5 + NaN + undefined,因此 value 也是 NaN;
下面來看一下第二次 b.next() value 輸出8,是傳遞了12做爲上一次yeild的值,y就變成了2*12=24,那24/3=8,因此value 爲 8;來看一下第三次b.next(),此時 x 爲 5 毫無爭議,z是傳遞進去的13,因爲上一次next傳入的值爲12,y=12*2=24,因此return(5+24+13)輸出42;
注意:next 方法的參數表示上一個 yield 語句的返回值,因此第一次使用 next 不能帶有參數
Generator函數返回的遍歷器對象,都有一個 throw 方法,能夠在函數體外拋出錯誤,而後在Generator函數體內捕獲,來看代碼:
JS code:
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
複製代碼
說明:上面代碼遍歷器 i 連續拋出兩個錯誤,第一個錯誤被 Generator 函數題內 catch 捕獲,i 的第二個錯誤因爲 Generator 函數內部的
Generator函數返回的遍歷器對象 return 方法,能夠返回給定的值,終結遍歷Generator函數。
JS code:
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
複製代碼
說明:遍歷器對象 g 調用 return 方法後,返回值 value 屬性就是 return 方法的參數 foo,同時,Generator函數的遍歷就終止了,done 值置爲true。
若是在Generater函數內部,調用另外一個Generator函數,默認狀況下是沒有效果的,來看代碼:
JS code:
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
function* gen() {
yield 'x';
yield* foo();
yield 'y'
}
for (let v of bar()){
console.log(v);
}// 'x'
// 'y'
for (let v of gen()){
console.log(v);
}
// 'x'
// 'a'
// 'b'
// 'y'複製代碼
說明:上面代碼在 bar 裏面調用 foo,是不會有效果的,要用到 yield* 語句,用來在一個Generator 函數裏面執行另外一個 Generator 函數。
Generator 函數老是返回一個遍歷器,ES6規定這個遍歷器是 Generator 函數的實例,也繼承了Generator 函數的 prototype 對象上的方法。
但若是把 Generator 函數看成普通的構造函數,並不會生效,同時 Generator 函數也不能跟new 命令一塊兒用,來看代碼:
JS code:
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
複製代碼
說明:能夠更換執行者 F 內部的 this 對象綁定對象,而後調用它,返回一個Iterator對象
那文章的最後仍是老規矩,歡迎你們點贊和糾錯。
祝各位週一愉快!