1.簡介 function命令與函數名之間有一個星號*; 函數體內部使用yield語句,定義遍歷器的每個成員,即不同的內部狀態。 總結: 調用Generator函數,返回一個部署了Iterator介面的遍歷器對象,用來操作內部指針。 以後,每次調用遍歷器對象的next方法,就會返回一個有著value ...
1.簡介
Generator函數是一個函數的內部狀態的遍歷器(也就是說,Generator函數是一個狀態機)。
形式上,Generator函數是一個普通函數,但是有兩個特征。
- function命令與函數名之間有一個星號*;
- 函數體內部使用yield語句,定義遍歷器的每個成員,即不同的內部狀態。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
let hw = helloWorldGenerator();
console.log(hw.next());// { value: 'hello', done: false }
console.log(hw.next());// { value: 'world', done: false }
console.log(hw.next());// { value: 'ending', done: true }
console.log(hw.next());// { value: undefined, done: true }
總結:
- 調用Generator函數,返回一個部署了Iterator介面的遍歷器對象,用來操作內部指針。
- 以後,每次調用遍歷器對象的next方法,就會返回一個有著value和done兩個屬性的對象。
- value屬性表示當前的內部狀態的值,是yield語句後面那個表達式的值;done屬性是一個布爾值,表示是否遍歷結束。
2.next方法的參數
yield語句本身沒有返回值,或者說總是返回undefined。next方法可以帶一個參數,該參數就會被當作上一個yield語句的返回值。
function* f() {
for(let i=0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
let g = f();
console.log(g.next()); // { value: 0, done: false }
console.log(g.next()); // { value: 1, done: false }
console.log(g.next(true));// { value: 0, done: false }
上面代碼先定義了一個可以無限運行的Generator函數f,如果next方法沒有參數,每次運行到yield語句,變數reset的值總是undefined。
當next方法帶一個參數true時,當前的變數reset就被重置為這個參數(即true),因此i會等於-1,下一輪迴圈就會從-1開始遞增。
3.for...of迴圈
for...of迴圈可以自動遍歷Generator函數,且此時不再需要調用next方法。
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
}
上面代碼使用for...of迴圈,依次顯示5個yield語句的值。
註意:
一旦next方法的返回對象的done屬性為true,for...of迴圈就會中止,且不包含該返回對象,所以上面代碼的return語句返回的6,不包括在for...of迴圈之中。
下麵是一個利用generator函數和for...of迴圈,實現斐波那契數列的例子。
function* fibonacci() {
let [prev, curr] = [0, 1];
for (;;) {
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for (let n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
從上面代碼可見,使用for...of語句時不需要使用next方法。
4.throw方法
Generator 函數內部還可以部署錯誤處理代碼,捕獲函數體外拋出的錯誤。
function* gen(x){
try {
var y = yield x + 2;
} catch (e){
console.log(e);
}
return y;
}
let g = gen(1);
g.next();
g.throw('出錯了');// 出錯了
上面代碼的最後一行,Generator 函數體外,使用指針對象的 throw 方法拋出的錯誤,可以被函數體內的 try ... catch 代碼塊捕獲。
這意味著,出錯的代碼與處理錯誤的代碼,實現了時間和空間上的分離,這對於非同步編程無疑是很重要的。
5.yield*語句
如果yield命令後面跟的是一個遍歷器,需要在yield命令後面加上星號,表明它返回的是一個遍歷器。這被稱為yield*語句。
其實yield關鍵字就是以一種更直觀、便捷的方式讓我們創建用於遍歷有限序列集合的迭代器,而yield則用於將生成器函數的代碼切片作為有限序列集合的元素(元素的類型為指令+數據,而不僅僅是數據而已)。下麵我們一起看看yield關鍵字是怎樣對代碼切片的吧!
定義生成器函數
function *enumerable(msg){
document.write(msg)
var msg1 = yield msg + ' after '
document.write(msg1)
var msg2 = yield msg1 + ' after'
document.write(msg2 + ' over')
}
//上述代碼最終會被解析為下麵的代碼:
var enumerable = function(msg){
var state = -1
return {
next: function(val){
switch(++state){
case 0:
document.write(msg + ' after')
break
case 1:
var msg1 = val
document.write(msg1 + ' after')
break
case 2:
var msg2 = val
document.write(msg2 + ' over')
break
}
}
}
}
6.作為對象屬性的Generator函數
如果一個對象的屬性是Generator函數,可以簡寫成下麵的形式。
let obj1 = {
* myGeneratorMethod() {
}
};
//上面代碼中,myGeneratorMethod屬性前面有一個星號,表示這個屬性是一個Generator函數。
//它的完整形式如下,與上面的寫法是等價的。
let obj2 = {
myGeneratorMethod: function* () {
}
};
此篇終,待續……