迭代是訪問集合元素的一種方法;可以被迭代的對象稱為可迭代對象;迭代器是一個可以記住遍歷位置的對象,迭代器對象從集合的第一個元素開始訪問,直到所有元素被訪問結(jié)束,迭代器只能前進(jìn)不會后退。
Lazy evaluation
Lazy evaluation常被譯為“延遲計算”或“惰性計算”,指的是僅僅在真正需要執(zhí)行的時候才計算表達(dá)式的值。
與惰性求值相反的是及早求值(eager evaluation)及早求值,也被稱為貪婪求值(greedy evaluation)或嚴(yán)格求值,是多數(shù)傳統(tǒng)編程語言的求值策略。
充分利用惰性求值的特性帶來的好處主要體現(xiàn)在以下兩個方面:
-
避免不必要的計算,帶來性能上的提升。
-
節(jié)省空間,使得無限循環(huán)的數(shù)據(jù)結(jié)構(gòu)成為可能。
迭代器
ES6 中的迭代器使惰性求值和創(chuàng)建用戶定義的數(shù)據(jù)序列成為可能。迭代是一種遍歷數(shù)據(jù)的機(jī)制。 迭代器是用于遍歷數(shù)據(jù)結(jié)構(gòu)元素(稱為Iterable)的指針,用于產(chǎn)生值序列的指針。
迭代器是一個可以被迭代的對象。它抽象了數(shù)據(jù)容器,使其行為類似于可迭代對象。
迭代器在實例化時不計算每個項目的值,僅在請求時才生成下一個值。 這非常有用,特別是對于大型數(shù)據(jù)集或無限個元素的序列。
可迭代對象
可迭代對象是希望其元素可被公眾訪問的數(shù)據(jù)結(jié)構(gòu)。JS 中的很多對象都是可迭代的,它們可能不是很好的察覺,但是如果仔細(xì)檢查,就會發(fā)現(xiàn)迭代的特征:
-
new Map([iterable])
-
new WeakMap([iterable])
-
new Set([iterable])
-
new WeakSet([iterable])
-
Promise.all([iterable])
-
Promise.race([iterable])
-
Array.from([iterable])
還有需要一個可迭代的對象,否則,它將拋出一個類型錯誤,例如:
-
for … of
-
… (展開操作符)
const [a, b, ..] = iterable (解構(gòu)賦值) -
yield* (生成器)
JavaScript中已有許多內(nèi)置的可迭代項:
String,Array,TypedArray,Map,Set。
迭代協(xié)議
迭代器和可迭對象遵循迭代協(xié)議。
協(xié)議是一組接口,并規(guī)定了如何使用它們。
迭代器遵循迭代器協(xié)議,可迭代遵循可迭代協(xié)議。
可迭代的協(xié)議
要使對象變得可迭代,它必須實現(xiàn)一個通過Symbol.iterator的迭代器方法,這個方法是迭代器的工廠。
使用 TypeScript,可迭代協(xié)議如下所示:
interface Iterable { [Symbol.iterator]() : Iterator; }
Symbol.iterator]()是無參數(shù)函數(shù)。 在可迭代對象上調(diào)用它,這意味著我們可以通過this來訪問可迭代對象,它可以是常規(guī)函數(shù)或生成器函數(shù)。
迭代器協(xié)議
迭代器協(xié)議定義了產(chǎn)生值序列的標(biāo)準(zhǔn)方法。
為了使對象成為迭代器,它必須實現(xiàn)next()方法。 迭代器可以實現(xiàn)return()方法,我們將在本文后面討論這個問題。
使用 TypeScript,迭代器協(xié)議如下所示:
interface Iterator { next() : IteratorResult; return?(value?: any): IteratorResult; }
IteratorResult 的定義如下:
interface IteratorResult { value?: any; done: boolean; }
-
done通知消費者迭代器是否已經(jīng)被使用,false表示仍有值需要生成,true表示迭代器已經(jīng)結(jié)束。
-
value 可以是任何 JS 值,它是向消費者展示的值。
當(dāng)done為true時,可以省略value。
組合
迭代器和可以可迭代對象可以用下面這張圖來表示:
事例
基礎(chǔ)知識介紹完了,接著,我們來配合一些事例來加深我們的映像。
范圍迭代器
我們先從一個非?;镜牡鏖_始,createRangeIterator迭代器。
我們手動調(diào)用it.next()以獲得下一個IteratorResult。 最后一次調(diào)用返回{done:true},這意味著迭代器現(xiàn)在已被使用,不再產(chǎn)生任何值。
function createRangeIterator(from, to) { let i = from; return { next() { if (i <= to) { return { value: i++, done: false }; } else { return { done: true }; } } } } const it = createRangeIterator(1, 3); console.log(it.next()); console.log(it.next()); console.log(it.next()); console.log(it.next());
可迭代范圍迭代器
在本文的前面,我已經(jīng)提到 JS 中的某些語句需要一個可迭代的對象。 因此,我們前面的示例在與for … of循環(huán)一起使用時將不起作用。
但是創(chuàng)建符合迭代器和可迭代協(xié)議的對象非常容易。
function createRangeIterator (from, to) { let i = from return { [Symbol.iterator] () { return this }, next() { if (i <= to) { return { value: i++, done: false } } else { return { done: true } } } } } const it = createRangeIterator(1, 3) for (const i of it) { console.log(i) }
無限序列迭代器
迭代器可以表示無限制大小的序列,因為它們僅在需要時才計算值。
注意不要在無限迭代器上使用擴(kuò)展運算符(…),JS 將嘗試消費迭代器,由于迭代器是無限的,因此它將永遠(yuǎn)不會結(jié)束。 所以你的應(yīng)用程序?qū)⒈罎?,因為?nèi)存已被耗盡
同樣,for … of 循環(huán)也是一樣的情況,所以要確保能退出循環(huán):
function createEvenNumbersIterator () { let value = 0 return { [Symbol.iterator] () { return this }, next () { value += 2 return { value, done: false} } } } const it = createEvenNumbersIterator() const [a, b, c] = it console.log({a, b, c}) const [x, y, z] = it console.log({ x, y, z }) for (const even of it) { console.log(even) if (even > 20) { break } }
關(guān)閉迭代器
前面我們提到過,迭代器可以有選擇地使用return()方法。 當(dāng)?shù)髦钡阶詈蠖紱]有迭代時使用此方法,并讓迭代器進(jìn)行清理。
for … of循環(huán)可以通過以下方式更早地終止迭代:
-
break
-
continue
-
throw
-
return
function createCloseableIterator () { let idx = 0 const data = ['a', 'b', 'c', 'd', 'e'] function cleanup() { console.log('Performing cleanup') } return { [Symbol.iterator]() { return this }, next () { if (idx <= data.length - 1) { return { value: data[idx++], done: false } } else { cleanup() return { done: true } } }, return () { cleanup() return { done: true } } } } const it = createCloseableIterator() for (const value of it) { console.log(value) if (value === 'c') { break } } console.log('n----------n') const _it = createCloseableIterator(); for (const value of _it) { console.log(value); }
-
如果知道迭代器已經(jīng)結(jié)束,則手動調(diào)用cleanup()函數(shù)。
-
如果突然完成,則return()起作用并為我們進(jìn)行清理。
【推薦學(xué)習(xí):javascript高級教程】