最近,為了更好地理解Redux Sagas的工作原理,我重學(xué)了JavaScript generators的知識,我把從網(wǎng)上收集到的各種知識點濃縮到一篇文章里,我希望這篇文章既通俗易懂,又足夠嚴謹,可以作為初學(xué)者的generators使用指南。
簡介
JavaScript在ES6時引入了生成器。生成器函數(shù)與常規(guī)函數(shù)類似,除了可以暫停和恢復(fù)它們這一點以外。生成器也與迭代器密切相關(guān),因為生成器對象就是迭代器。
在JavaScript中,函數(shù)調(diào)用后通常不能暫停或停止。(是的,異步函數(shù)在等待await語句時暫停,但是異步函數(shù)在ES7時才引入。此外,異步函數(shù)是建立在生成器之上的。)一個普通函數(shù)只有在返回或拋出錯誤時才會結(jié)束。
function foo() { console.log('Starting'); const x = 42; console.log(x); console.log('Stop me if you can'); console.log('But you cannot'); }
相反,生成器允許我們在任意斷點處暫停執(zhí)行,并從同一斷點恢復(fù)執(zhí)行。
生成器和迭代器
來自MDN:
在JavaScript中,迭代器是一個對象,它定義一個序列,并在終止時可能返回一個返回值。 >更具體地說,迭代器是通過使用 next() 方法實現(xiàn) Iterator protocol >的任何一個對象,該方法返回具有兩個屬性的對象: value,這是序列中的 next 值;和 done, 如果已經(jīng)迭代到序列中的最后一個值,則它為 true 。如果 value 和 done 一起存在,則它是迭代器的返回值。
因此,迭代器的本質(zhì)就是:
- 定義序列的對象
- 有一個
next()
方法… - 返回一個具有兩個屬性的對象:value和done
是否需要生成器來創(chuàng)建迭代器?不。事實上,我們已經(jīng)可以使用閉包pre-ES6創(chuàng)建一個無限的斐波那契數(shù)列,如下例所示:
var fibonacci = { next: (function () { var pre = 0, cur = 1; return function () { tmp = pre; pre = cur; cur += tmp; return cur; }; })() }; fibonacci.next(); // 1 fibonacci.next(); // 2 fibonacci.next(); // 3 fibonacci.next(); // 5 fibonacci.next(); // 8
關(guān)于生成器的好處,我將再次引用MDN:
雖然自定義迭代器是一個有用的工具,但是由于需要顯式地維護它們的內(nèi)部狀態(tài),創(chuàng)建它們需要我們仔細地編程。生成器函數(shù)提供了一個強大的替代方法:它們允許我們通過編寫一個執(zhí)行不是連續(xù)的函數(shù)來定義迭代算法。
換句話說,使用生成器創(chuàng)建迭代器更簡單(不需要閉包!),這意味著出錯的可能性更小。
生成器和迭代器之間的關(guān)系就是生成器函數(shù)返回的生成器對象是迭代器。
語法
生成器函數(shù)使用function *語法創(chuàng)建,并使用yield關(guān)鍵字暫停。
最初調(diào)用生成器函數(shù)并不執(zhí)行它的任何代碼;相反,它返回一個生成器對象。該值通過調(diào)用生成器的next()方法來使用,該方法執(zhí)行代碼,直到遇到y(tǒng)ield關(guān)鍵字,然后暫停,直到再次調(diào)用next()。
function * makeGen() { yield 'Hello'; yield 'World'; } const g = makeGen(); // g is a generator g.next(); // { value: 'Hello', done: false } g.next(); // { value: 'World', done: false } g.next(); // { value: undefined, done: true }
在上面的最后一個語句之后重復(fù)調(diào)用g.next()只會返回(或者更準(zhǔn)確地說,產(chǎn)生)相同的返回對象:{ value: undefined, done: true }。
yield暫停執(zhí)行
大家可能會注意到上面的代碼片段有一些特殊之處。第二個next()調(diào)用生成一個對象,該對象的屬性為done: false,而不是done: true。
既然我們正在生成器函數(shù)中執(zhí)行最后一條語句,那么done屬性不應(yīng)該為true嗎?并不是的。當(dāng)遇到y(tǒng)ield語句時,它后面的值(在本例中是“World”)被生成,執(zhí)行暫停。因此,第二個next()調(diào)用暫停在第二個yield語句上,因此執(zhí)行還沒有完成—只有在第二個yield語句之后執(zhí)行重新開始時,執(zhí)行才算完成(即done: true),并且不再運行代碼。
我們可以將next()調(diào)用看作是告訴程序運行到下一個yield語句(假設(shè)它存在)、生成一個值并暫停。程序在恢復(fù)執(zhí)行之前不會知道yield語句之后沒有任何內(nèi)容,并且只能通過另一個next()調(diào)用恢復(fù)執(zhí)行。
yield和return
在上面的示例中,我們使用yield將值傳遞給生成器外部。我們也可以使用return(就像在普通函數(shù)中一樣);但是,使用return可以終止執(zhí)行并設(shè)置done: true。
function * makeGen() { yield 'Hello'; return 'Bye'; yield 'World'; } const g = makeGen(); // g is a generator g.next(); // { value: 'Hello', done: false } g.next(); // { value: 'Bye', done: true } g.next(); // { value: undefined, done: true }
因為執(zhí)行不會在return語句上暫停,而且根據(jù)定義,在return語句之后不能執(zhí)行任何代碼,所以done被設(shè)置為true。
yield:next方法的參數(shù)
到目前為止,我們一直在使用yield傳遞生成器外部的值(并暫停其執(zhí)行)。
然而,yield實際上是雙向的,并且允許我們將值傳遞到生成器函數(shù)中。
function * makeGen() { const foo = yield 'Hello world'; console.log(foo); } const g = makeGen(); g.next(1); // { value: 'Hello world', done: false } g.next(2); // logs 2, yields { value: undefined, done: true }
等一下。不應(yīng)該是"1"打印到控制臺,但是控制臺打印的是"2"?起初,我發(fā)現(xiàn)這部分在概念上與直覺相反,因為我預(yù)期的賦值foo = 1。畢竟,我們將“1”傳遞到next()方法調(diào)用中,從而生成Hello world,對嗎?
但事實并非如此。傳遞給第一個next(…)調(diào)用的值將被丟棄。除了這似乎是ES6規(guī)范之外,實際上沒有其他原因.從語義上講,第一個next方法用來啟動遍歷器對象,所以不用帶有參數(shù)。
我喜歡這樣對程序的執(zhí)行進行合理化:
- 在第一個next()調(diào)用時,它將一直運行,直到遇到y(tǒng)ield 'Hello world',在此基礎(chǔ)上生成{ value: 'Hello world', done: false }和暫停。就是這么回事。正如大家所看到的,傳遞給第一個next()調(diào)用的任何值都是不會被使用的(因此被丟棄)。
- 當(dāng)再次調(diào)用next(…)時,執(zhí)行將恢復(fù)。在這種情況下,執(zhí)行需要為常量foo分配一些值(由yield語句決定)。因此,我們對next(2)的第二次調(diào)用賦值foo=2。程序不會在這里停止—它會一直運行,直到遇到下一個yield或return語句。在本例中,沒有
相關(guān)推薦
- 華納云香港高防服務(wù)器150G防御4.6折促銷,低至6888元/月,CN2大帶寬直連清洗,終身循環(huán)折扣
- 2025年國內(nèi)免費AI工具推薦:文章生成與圖像創(chuàng)作全攻略
- AI時代,個人站長如何用AI工具實現(xiàn)“一人公司”
- 個人站長消亡論?從“消失”到“重生”的三大破局路徑
- raksmart法蘭克福云服務(wù)器延遲高嗎?
- 華納云高防服務(wù)器3.6折起低至1188元/月,企業(yè)級真實防御20G`T級,自營機房一手服務(wù)器資源
- 服務(wù)器的系統(tǒng)和普通電腦系統(tǒng)一樣嗎?
- RakSmart法蘭克福數(shù)據(jù)中心優(yōu)勢與適用場景