JavaScript中的迭代器和生成器詳解
來源:易賢網(wǎng) 閱讀:614 次 日期:2014-11-03 14:11:59
溫馨提示:易賢網(wǎng)小編為您整理了“JavaScript中的迭代器和生成器詳解”,方便廣大網(wǎng)友查閱!

處理集合里的每一項(xiàng)是一個(gè)非常普通的操作,JavaScript提供了許多方法來迭代一個(gè)集合,從簡(jiǎn)單的for和for each循環(huán)到 map(),filter() 和 array comprehensions(數(shù)組推導(dǎo)式)。在JavaScript 1.7中,迭代器和生成器在JavaScript核心語法中帶來了新的迭代機(jī)制,而且還提供了定制 for…in 和 for each 循環(huán)行為的機(jī)制。

迭代器

迭代器是一個(gè)每次訪問集合序列中一個(gè)元素的對(duì)象,并跟蹤該序列中迭代的當(dāng)前位置。在JavaScript中迭代器是一個(gè)對(duì)象,這個(gè)對(duì)象提供了一個(gè) next() 方法,next() 方法返回序列中的下一個(gè)元素。當(dāng)序列中所有元素都遍歷完成時(shí),該方法拋出 StopIteration 異常。

迭代器對(duì)象一旦被建立,就可以通過顯式的重復(fù)調(diào)用next(),或者使用JavaScript的 for…in 和 for each 循環(huán)隱式調(diào)用。

簡(jiǎn)單的對(duì)對(duì)象和數(shù)組進(jìn)行迭代的迭代器可以使用 Iterator() 被創(chuàng)建:

代碼如下:

var lang = { name: 'JavaScript', birthYear: 1995 };

var it = Iterator(lang);

一旦初始化完成,next() 方法可以被調(diào)用來依次訪問對(duì)象的鍵值對(duì):

代碼如下:

var pair = it.next(); //鍵值對(duì)是["name", "JavaScript"]

pair = it.next(); //鍵值對(duì)是["birthday", 1995]

pair = it.next(); //一個(gè) `StopIteration` 異常被拋出

for…in 循環(huán)可以被用來替換顯式的調(diào)用 next() 方法。當(dāng) StopIteration 異常被拋出時(shí),循環(huán)會(huì)自動(dòng)終止。

代碼如下:

var it = Iterator(lang);

for (var pair in it)

print(pair); //每次輸出 it 中的一個(gè) [key, value] 鍵值對(duì)

如果你只想迭代對(duì)象的 key 值,可以往 Iterator() 函數(shù)中傳入第二個(gè)參數(shù),值為 true:

代碼如下:

var it = Iterator(lang, true);

for (var key in it)

print(key); //每次輸出 key 值

使用 Iterator() 訪問對(duì)象的一個(gè)好處是,被添加到 Object.prototype 的自定義屬性不會(huì)被包含在序列對(duì)象中。

Iterator() 同樣可以被作用在數(shù)組上:

代碼如下:

var langs = ['JavaScript', 'Python', 'Haskell'];

var it = Iterator(langs);

for (var pair in it)

print(pair); //每次迭代輸出 [index, language] 鍵值對(duì)

就像遍歷對(duì)象一樣,把 true 當(dāng)做第二個(gè)參數(shù)傳入遍歷的結(jié)果將會(huì)是數(shù)組索引:

var langs = ['JavaScript', 'Python', 'Haskell'];

var it = Iterator(langs, true);

for (var i in it)

print(i); //輸出 0,然后是 1,然后是 2

使用 let 關(guān)鍵字可以在循環(huán)內(nèi)部分別分配索引和值給塊變量,還可以解構(gòu)賦值(Destructuring Assignment):

代碼如下:

var langs = ['JavaScript', 'Python', 'Haskell'];

var it = Iterators(langs);

for (let [i, lang] in it)

print(i + ': ' + lang); //輸出 "0: JavaScript" 等

聲明自定義迭代器

一些代表元素集合的對(duì)象應(yīng)該用一種指定的方式來迭代。

1.迭代一個(gè)表示范圍(Range)的對(duì)象應(yīng)該一個(gè)接一個(gè)的返回這個(gè)范圍包含的數(shù)字

2.一個(gè)樹的葉子節(jié)點(diǎn)可以使用深度優(yōu)先或者廣度優(yōu)先訪問到

3.迭代一個(gè)代表數(shù)據(jù)庫(kù)查詢結(jié)果的對(duì)象應(yīng)該一行一行的返回,即使整個(gè)結(jié)果集尚未全部加載到一個(gè)單一數(shù)組

4.作用在一個(gè)無限數(shù)學(xué)序列(像斐波那契序列)上的迭代器應(yīng)該在不創(chuàng)建無限長(zhǎng)度數(shù)據(jù)結(jié)構(gòu)的前提下一個(gè)接一個(gè)的返回結(jié)果

JavaScript 允許你寫自定義迭代邏輯的代碼,并把它作用在一個(gè)對(duì)象上

我們創(chuàng)建一個(gè)簡(jiǎn)單的 Range 對(duì)象,包含低和高兩個(gè)值:

代碼如下:

function Range(low, high){

this.low = low;

this.high = high;

}

現(xiàn)在我們創(chuàng)建一個(gè)自定義迭代器,它返回一個(gè)包含范圍內(nèi)所有整數(shù)的序列。迭代器接口需要我們提供一個(gè) next() 方法用來返回序列中的下一個(gè)元素或者是拋出 StopIteration 異常。

代碼如下:

function RangeIterator(range){

this.range = range;

this.current = this.range.low;

}

RangeIterator.prototype.next = function(){

if (this.current > this.range.high)

throw StopIteration;

else

return this.current++;

};

我們的 RangeIterator 通過 range 實(shí)例來實(shí)例化,同時(shí)維持一個(gè) current 屬性來跟蹤當(dāng)前序列的位置。

最后,為了讓 RangeIterator 可以和 Range 結(jié)合起來,我們需要為 Range 添加一個(gè)特殊的 __iterator__ 方法。當(dāng)我們?cè)噲D去迭代一個(gè) Range 時(shí),它將被調(diào)用,而且應(yīng)該返回一個(gè)實(shí)現(xiàn)了迭代邏輯的 RangeIterator 實(shí)例。

代碼如下:

Range.prototype.__iterator__ = function(){

return new RangeIterator(this);

};

完成我們的自定義迭代器后,我們就可以迭代一個(gè)范圍實(shí)例:

代碼如下:

var range = new Range(3, 5);

for (var i in range)

print(i); //輸出 3,然后 4,然后 5

生成器:一種更好的方式來構(gòu)建迭代器

雖然自定義的迭代器是一種很有用的工具,但是創(chuàng)建它們的時(shí)候要仔細(xì)規(guī)劃,因?yàn)樾枰@式的維護(hù)它們的內(nèi)部狀態(tài)。

生成器提供了很強(qiáng)大的功能:它允許你定義一個(gè)包含自有迭代算法的函數(shù), 同時(shí)它可以自動(dòng)維護(hù)自己的狀態(tài)。

生成器是可以作為迭代器工廠的特殊函數(shù)。如果一個(gè)函數(shù)包含了一個(gè)或多個(gè) yield 表達(dá)式,那么就稱它為生成器(譯者注:Node.js 還需要在函數(shù)名前加 * 來表示)。

注意:只有 HTML 中被包含在 <script type="application/javascript;version=1.7"> (或者更高版本)中的代碼塊才可以使用 yield 關(guān)鍵字。XUL (XML User Interface Language) 腳本標(biāo)簽不需要指定這個(gè)特殊的代碼塊也可以訪問這些特性。

當(dāng)一個(gè)生成器函數(shù)被調(diào)用時(shí),函數(shù)體不會(huì)即刻執(zhí)行,它會(huì)返回一個(gè) generator-iterator 對(duì)象。每次調(diào)用 generator-iterator 的 next() 方法,函數(shù)體就會(huì)執(zhí)行到下一個(gè) yield 表達(dá)式,然后返回它的結(jié)果。當(dāng)函數(shù)結(jié)束或者碰到 return 語句,一個(gè) StopIteration 異常會(huì)被拋出。

用一個(gè)例子來更好的說明:

代碼如下:

function simpleGenerator(){

yield "first";

yield "second";

yield "third";

for (var i = 0; i < 3; i++)

yield i;

}

var g = simpleGenerator();

print(g.next()); //輸出 "first"

print(g.next()); //輸出 "second"

print(g.next()); //輸出 "third"

print(g.next()); //輸出 0

print(g.next()); //輸出 1

print(g.next()); //輸出 2

print(g.next()); //拋出 StopIteration 異常

生成器函數(shù)可以被一個(gè)類直接的當(dāng)做 __iterator__ 方法使用,在需要自定義迭代器的地方可以有效的減少代碼量。我們使用生成器重寫一下 Range :

代碼如下:

function Range(low, high){

this.low = low;

this.high = high;

}

Range.prototype.__iterator__ = function(){

for (var i = this.low; i <= this.high; i++)

yield i;

};

var range = new Range(3, 5);

for (var i in range)

print(i); //輸出 3,然后 4,然后 5

不是所有的生成器都會(huì)終止,你可以創(chuàng)建一個(gè)代表無限序列的生成器。下面的生成器實(shí)現(xiàn)一個(gè)斐波那契序列,就是每一個(gè)元素都是前面兩個(gè)的和:

代碼如下:

function fibonacci(){

var fn1 = 1;

var fn2 = 1;

while (1) {

var current = fn2;

fn2 = fn1;

fn1 = fn1 + current;

yield current;

}

}

var sequence = fibonacci();

print(sequence.next()); // 1

print(sequence.next()); // 1

print(sequence.next()); // 2

print(sequence.next()); // 3

print(sequence.next()); // 5

print(sequence.next()); // 8

print(sequence.next()); // 13

生成器函數(shù)可以帶有參數(shù),并且會(huì)在第一次調(diào)用函數(shù)時(shí)使用這些參數(shù)。生成器可以被終止(引起它拋出 StopIteration 異常)通過使用 return 語句。下面的 fibonacci() 變體帶有一個(gè)可選的 limit 參數(shù),當(dāng)條件被觸發(fā)時(shí)終止函數(shù)。

代碼如下:

function fibonacci(limit){

var fn1 = 1;

var fn2 = 1;

while(1){

var current = fn2;

fn2 = fn1;

fn1 = fn1 + current;

if (limit && current > limit)

return;

yield current;

}

}

生成器高級(jí)特性

生成器可以根據(jù)需求計(jì)算yield返回值,這使得它可以表示以前昂貴的序列計(jì)算需求,甚至是上面所示的無限序列。

除了 next() 方法,generator-iterator 對(duì)象還有一個(gè) send() 方法,該方法可以修改生成器的內(nèi)部狀態(tài)。傳給 send() 的值將會(huì)被當(dāng)做最后一個(gè) yield 表達(dá)式的結(jié)果,并且會(huì)暫停生成器。在你使用 send() 方法傳一個(gè)指定值前,你必須至少調(diào)用一次 next() 來啟動(dòng)生成器。

下面的斐波那契生成器使用 send() 方法來重啟序列:

代碼如下:

function fibonacci(){

var fn1 = 1;

var fn2 = 1;

while (1) {

var current = fn2;

fn2 = fn1;

fn1 = fn1 + current;

var reset = yield current;

if (reset) {

fn1 = 1;

fn2 = 1;

}

}

}

var sequence = fibonacci();

print(sequence.next()); //1

print(sequence.next()); //1

print(sequence.next()); //2

print(sequence.next()); //3

print(sequence.next()); //5

print(sequence.next()); //8

print(sequence.next()); //13

print(sequence.send(true)); //1

print(sequence.next()); //1

print(sequence.next()); //2

print(sequence.next()); //3

注意:有意思的一點(diǎn)是,調(diào)用 send(undefined) 和調(diào)用 next() 是完全同等的。不過,當(dāng)調(diào)用 send() 方法啟動(dòng)一個(gè)新的生成器時(shí),除了 undefined 其它的值都會(huì)拋出一個(gè) TypeError 異常。

你可以調(diào)用 throw 方法并且傳遞一個(gè)它應(yīng)該拋出的異常值來強(qiáng)制生成器拋出一個(gè)異常。此異常將從當(dāng)前上下文拋出并暫停生成器,類似當(dāng)前的 yield 執(zhí)行,只不過換成了 throw value 語句。

如果在拋出異常的處理過程中沒有遇到 yield ,該異常將會(huì)被傳遞直到調(diào)用 throw() 方法,并且隨后調(diào)用 next() 將會(huì)導(dǎo)致 StopIteration 異常被拋出。

生成器擁有一個(gè) close() 方法來強(qiáng)制生成器結(jié)束。結(jié)束一個(gè)生成器會(huì)產(chǎn)生如下影響:

1.所有生成器中有效的 finally 字句將會(huì)執(zhí)行

2.如果 finally 字句拋出了除 StopIteration 以外的任何異常,該異常將會(huì)被傳遞到 close() 方法的調(diào)用者

3.生成器會(huì)終止

生成器表達(dá)式

數(shù)組推導(dǎo)式 的一個(gè)明顯缺點(diǎn)是,它們會(huì)導(dǎo)致整個(gè)數(shù)組在內(nèi)存中構(gòu)造。當(dāng)輸入到推導(dǎo)式的本身是個(gè)小數(shù)組時(shí)它的開銷是微不足道的—但是,當(dāng)輸入數(shù)組很大或者創(chuàng)建一個(gè)新的昂貴(或者是無限的)數(shù)組生成器時(shí)就可能出現(xiàn)問題。

生成器允許對(duì)序列延遲計(jì)算(lazy computation),在需要時(shí)按需計(jì)算元素。生成器表達(dá)式在句法上幾乎和數(shù)組推導(dǎo)式相同—它用圓括號(hào)來代替方括號(hào)(而且用 for...in 代替 for each...in)—但是它創(chuàng)建一個(gè)生成器而不是數(shù)組,這樣就可以延遲計(jì)算。你可以把它想象成創(chuàng)建生成器的簡(jiǎn)短語法。

假設(shè)我們有一個(gè)迭代器 it 來迭代一個(gè)巨大的整數(shù)序列。我們需要?jiǎng)?chuàng)建一個(gè)新的迭代器來迭代偶數(shù)。一個(gè)數(shù)組推導(dǎo)式將會(huì)在內(nèi)存中創(chuàng)建整個(gè)包含所有偶數(shù)的數(shù)組:

代碼如下:

var doubles = [i * 2 for (i in it)];

而生成器表達(dá)式將會(huì)創(chuàng)建一個(gè)新的迭代器,并且在需要的時(shí)候按需來計(jì)算偶數(shù)值:

代碼如下:

var it2 = (i * 2 for (i in it));

print(it2.next()); //it 里面的第一個(gè)偶數(shù)

print(it2.next()); //it 里面的第二個(gè)偶數(shù)

當(dāng)一個(gè)生成器被用做函數(shù)的參數(shù),圓括號(hào)被用做函數(shù)調(diào)用,意味著最外層的圓括號(hào)可以被省略:

代碼如下:

var result = doSomething(i * 2 for (i in it));

更多信息請(qǐng)查看IT技術(shù)專欄

更多信息請(qǐng)查看腳本欄目
易賢網(wǎng)手機(jī)網(wǎng)站地址:JavaScript中的迭代器和生成器詳解
由于各方面情況的不斷調(diào)整與變化,易賢網(wǎng)提供的所有考試信息和咨詢回復(fù)僅供參考,敬請(qǐng)考生以權(quán)威部門公布的正式信息和咨詢?yōu)闇?zhǔn)!

2025國(guó)考·省考課程試聽報(bào)名

  • 報(bào)班類型
  • 姓名
  • 手機(jī)號(hào)
  • 驗(yàn)證碼
關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡(jiǎn)要咨詢 | 簡(jiǎn)要咨詢須知 | 加入群交流 | 手機(jī)站點(diǎn) | 投訴建議
工業(yè)和信息化部備案號(hào):滇ICP備2023014141號(hào)-1 云南省教育廳備案號(hào):云教ICP備0901021 滇公網(wǎng)安備53010202001879號(hào) 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號(hào)
云南網(wǎng)警備案專用圖標(biāo)
聯(lián)系電話:0871-65099533/13759567129 獲取招聘考試信息及咨詢關(guān)注公眾號(hào):hfpxwx
咨詢QQ:526150442(9:00—18:00)版權(quán)所有:易賢網(wǎng)
云南網(wǎng)警報(bào)警專用圖標(biāo)