generator
generator(生成器)是ES6標(biāo)準(zhǔn)引入的新的數(shù)據(jù)類(lèi)型识补。一個(gè)generator看上去像一個(gè)函數(shù),但可以返回多次辫红。
ES6定義generator標(biāo)準(zhǔn)的哥們借鑒了Python的generator的概念和語(yǔ)法李请,如果你對(duì)Python的generator很熟悉,那么ES6的generator就是小菜一碟了厉熟。如果你對(duì)Python還不熟,趕快惡補(bǔ)Python教程较幌!揍瑟。
我們先復(fù)習(xí)函數(shù)的概念。一個(gè)函數(shù)是一段完整的代碼乍炉,調(diào)用一個(gè)函數(shù)就是傳入?yún)?shù)绢片,然后返回結(jié)果:
function foo(x) {
return x + x;
}
var r = foo(1); // 調(diào)用foo函數(shù)
函數(shù)在執(zhí)行過(guò)程中,如果沒(méi)有遇到return
語(yǔ)句(函數(shù)末尾如果沒(méi)有return
岛琼,就是隱含的return undefined;
)底循,控制權(quán)無(wú)法交回被調(diào)用的代碼。
generator跟函數(shù)很像槐瑞,定義如下:
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
generator和函數(shù)不同的是熙涤,generator由function*
定義(注意多出的*
號(hào)),并且,除了return
語(yǔ)句祠挫,還可以用yield
返回多次那槽。
大多數(shù)同學(xué)立刻就暈了,generator就是能夠返回多次的“函數(shù)”等舔?返回多次有啥用骚灸?
還是舉個(gè)栗子吧。
我們以一個(gè)著名的斐波那契數(shù)列為例慌植,它由0
甚牲,1
開(kāi)頭:
0 1 1 2 3 5 8 13 21 34 ...
要編寫(xiě)一個(gè)產(chǎn)生斐波那契數(shù)列的函數(shù),可以這么寫(xiě):
function fib(max) {
var
t,
a = 0,
b = 1,
arr = [0, 1];
while (arr.length < max) {
[a, b] = [b, a + b];
arr.push(b);
}
return arr;
}
// 測(cè)試:
fib(5); // [0, 1, 1, 2, 3]
fib(10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
函數(shù)只能返回一次蝶柿,所以必須返回一個(gè)Array
丈钙。但是,如果換成generator只锭,就可以一次返回一個(gè)數(shù)著恩,不斷返回多次。用generator改寫(xiě)如下:
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}
直接調(diào)用試試:
fib(5); // fib {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}
直接調(diào)用一個(gè)generator和調(diào)用函數(shù)不一樣蜻展,fib(5)
僅僅是創(chuàng)建了一個(gè)generator對(duì)象喉誊,還沒(méi)有去執(zhí)行它。
調(diào)用generator對(duì)象有兩個(gè)方法纵顾,一是不斷地調(diào)用generator對(duì)象的next()
方法:
var f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}
next()
方法會(huì)執(zhí)行g(shù)enerator的代碼伍茄,然后,每次遇到yield x;
就返回一個(gè)對(duì)象{value: x, done: true/false}
施逾,然后“暫头蠼茫”。返回的value
就是yield
的返回值汉额,done
表示這個(gè)generator是否已經(jīng)執(zhí)行結(jié)束了曹仗。如果done
為true
,則value
就是return
的返回值蠕搜。
當(dāng)執(zhí)行到done
為true
時(shí)怎茫,這個(gè)generator對(duì)象就已經(jīng)全部執(zhí)行完畢,不要再繼續(xù)調(diào)用next()
了妓灌。
第二個(gè)方法是直接用for ... of
循環(huán)迭代generator對(duì)象轨蛤,這種方式不需要我們自己判斷done
:
'use strict'
function* fib(max) {
var
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}
for (var x of fib(10)) {
console.log(x); // 依次輸出0, 1, 1, 2, 3, ...
}
Run
0
1
1
2
3
5
8
13
21
34
generator和普通函數(shù)相比,有什么用虫埂?
因?yàn)間enerator可以在執(zhí)行過(guò)程中多次返回祥山,所以它看上去就像一個(gè)可以記住執(zhí)行狀態(tài)的函數(shù),利用這一點(diǎn)掉伏,寫(xiě)一個(gè)generator就可以實(shí)現(xiàn)需要用面向?qū)ο蟛拍軐?shí)現(xiàn)的功能缝呕。例如澳窑,用一個(gè)對(duì)象來(lái)保存狀態(tài),得這么寫(xiě):
var fib = {
a: 0,
b: 1,
n: 0,
max: 5,
next: function () {
var
r = this.a,
t = this.a + this.b;
this.a = this.b;
this.b = t;
if (this.n < this.max) {
this.n ++;
return r;
} else {
return undefined;
}
}
};
用對(duì)象的屬性來(lái)保存狀態(tài)岳颇,相當(dāng)繁瑣照捡。
generator還有另一個(gè)巨大的好處,就是把異步回調(diào)代碼變成“同步”代碼话侧。這個(gè)好處要等到后面學(xué)了AJAX以后才能體會(huì)到栗精。
沒(méi)有g(shù)enerator之前的黑暗時(shí)代,用AJAX時(shí)需要這么寫(xiě)代碼:
ajax('http://url-1', data1, function (err, result) {
if (err) {
return handle(err);
}
ajax('http://url-2', data2, function (err, result) {
if (err) {
return handle(err);
}
ajax('http://url-3', data3, function (err, result) {
if (err) {
return handle(err);
}
return success(result);
});
});
});
回調(diào)越多瞻鹏,代碼越難看悲立。
有了generator的美好時(shí)代,用AJAX時(shí)可以這么寫(xiě):
try {
r1 = yield ajax('http://url-1', data1);
r2 = yield ajax('http://url-2', data2);
r3 = yield ajax('http://url-3', data3);
success(r3);
}
catch (err) {
handle(err);
}
看上去是同步的代碼新博,實(shí)際執(zhí)行是異步的薪夕。