JavaScript Lazy evaluation:可迭代對(duì)象與迭代器
本文已經(jīng)過(guò)原作者 MelkorNemesis 授權(quán)翻譯。
Lazy evaluation
Lazy evaluation常被譯為“延遲計(jì)算”或“惰性計(jì)算”,指的是僅僅在真正需要執(zhí)行的時(shí)候才計(jì)算表達(dá)式的值。
與惰性求值相反的是及早求值(eager evaluation)及早求值,也被稱為貪婪求值(greedy evaluation)或嚴(yán)格求值,是多數(shù)傳統(tǒng)編程語(yǔ)言的求值策略。
充分利用惰性求值的特性帶來(lái)的好處主要體現(xiàn)在以下兩個(gè)方面:
- 避免不必要的計(jì)算,帶來(lái)性能上的提升。
 - 節(jié)省空間,使得無(wú)限循環(huán)的數(shù)據(jù)結(jié)構(gòu)成為可能。
 
迭代器
ES6 中的迭代器使惰性求值和創(chuàng)建用戶定義的數(shù)據(jù)序列成為可能。迭代是一種遍歷數(shù)據(jù)的機(jī)制。迭代器是用于遍歷數(shù)據(jù)結(jié)構(gòu)元素(稱為Iterable)的指針,用于產(chǎn)生值序列的指針。
迭代器是一個(gè)可以被迭代的對(duì)象。它抽象了數(shù)據(jù)容器,使其行為類(lèi)似于可迭代對(duì)象。
迭代器在實(shí)例化時(shí)不計(jì)算每個(gè)項(xiàng)目的值,僅在請(qǐng)求時(shí)才生成下一個(gè)值。這非常有用,特別是對(duì)于大型數(shù)據(jù)集或無(wú)限個(gè)元素的序列。
可迭代對(duì)象
可迭代對(duì)象是希望其元素可被公眾訪問(wèn)的數(shù)據(jù)結(jié)構(gòu)。JS 中的很多對(duì)象都是可迭代的,它們可能不是很好的察覺(jué),但是如果仔細(xì)檢查,就會(huì)發(fā)現(xiàn)迭代的特征:
- new Map([iterable])
 - new WeakMap([iterable])
 - new Set([iterable])
 - new WeakSet([iterable])
 - Promise.all([iterable])
 - Promise.race([iterable])
 - Array.from([iterable])
 
還有需要一個(gè)可迭代的對(duì)象,否則,它將拋出一個(gè)類(lèi)型錯(cuò)誤,例如:
- for ... of
 - ... (展開(kāi)操作符)const [a, b, ..] = iterable (解構(gòu)賦值)
 - yield* (生成器)
 
JavaScript中已有許多內(nèi)置的可迭代項(xiàng):
String,Array,TypedArray,Map,Set。
迭代協(xié)議
迭代器和可迭對(duì)象遵循迭代協(xié)議。
協(xié)議是一組接口,并規(guī)定了如何使用它們。
迭代器遵循迭代器協(xié)議,可迭代遵循可迭代協(xié)議。
可迭代的協(xié)議
要使對(duì)象變得可迭代,它必須實(shí)現(xiàn)一個(gè)通過(guò)Symbol.iterator的迭代器方法,這個(gè)方法是迭代器的工廠。
使用 TypeScript,可迭代協(xié)議如下所示:
- interface Iterable {
 - [Symbol.iterator]() : Iterator;
 - }
 
Symbol.iterator]()是無(wú)參數(shù)函數(shù)。在可迭代對(duì)象上調(diào)用它,這意味著我們可以通過(guò)this來(lái)訪問(wèn)可迭代對(duì)象,它可以是常規(guī)函數(shù)或生成器函數(shù)。
迭代器協(xié)議
迭代器協(xié)議定義了產(chǎn)生值序列的標(biāo)準(zhǔn)方法。
為了使對(duì)象成為迭代器,它必須實(shí)現(xiàn)next()方法。迭代器可以實(shí)現(xiàn)return()方法,我們將在本文后面討論這個(gè)問(wèn)題。
使用 TypeScript,迭代器協(xié)議如下所示:
- interface Iterator {
 - next() : IteratorResult;
 - return?(value?: any): IteratorResult;
 - }
 
IteratorResult 的定義如下:
- interface IteratorResult {
 - value?: any;
 - done: boolean;
 - }
 
- done通知消費(fèi)者迭代器是否已經(jīng)被使用,false表示仍有值需要生成,true表示迭代器已經(jīng)結(jié)束。
 - value 可以是任何 JS 值,它是向消費(fèi)者展示的值。
 
當(dāng)done為true時(shí),可以省略value。
組合
迭代器和可以可迭代對(duì)象可以用下面這張圖來(lái)表示:
事例
基礎(chǔ)知識(shí)介紹完了,接著,我們來(lái)配合一些事例來(lái)加深我們的映像。
范圍迭代器
我們先從一個(gè)非?;镜牡鏖_(kāi)始,createRangeIterator迭代器。
我們手動(dòng)調(diào)用it.next()以獲得下一個(gè)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 中的某些語(yǔ)句需要一個(gè)可迭代的對(duì)象。因此,我們前面的示例在與for ... of循環(huán)一起使用時(shí)將不起作用。
但是創(chuàng)建符合迭代器和可迭代協(xié)議的對(duì)象非常容易。
- 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)
 - }
 
無(wú)限序列迭代器
迭代器可以表示無(wú)限制大小的序列,因?yàn)樗鼈儍H在需要時(shí)才計(jì)算值。
注意不要在無(wú)限迭代器上使用擴(kuò)展運(yùn)算符(...),JS 將嘗試消費(fèi)迭代器,由于迭代器是無(wú)限的,因此它將永遠(yuǎn)不會(huì)結(jié)束。所以你的應(yīng)用程序?qū)⒈罎ⅲ驗(yàn)閮?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)閉迭代器
前面我們提到過(guò),迭代器可以有選擇地使用return()方法。當(dāng)?shù)髦钡阶詈蠖紱](méi)有迭代時(shí)使用此方法,并讓迭代器進(jìn)行清理。
for ... of循環(huán)可以通過(guò)以下方式更早地終止迭代:
- 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é)束,則手動(dòng)調(diào)用cleanup()函數(shù)。
 - 如果突然完成,則return()起作用并為我們進(jìn)行清理。
 
額外的內(nèi)容
如果你已經(jīng)做到了這一點(diǎn),我們來(lái)看看一些額外的內(nèi)容。
組合器
組合器是將現(xiàn)有可迭代對(duì)象組合在一起以創(chuàng)建新可迭代對(duì)象的函數(shù)。
因此,我們能夠創(chuàng)建許多實(shí)用函數(shù)。那map或者filter呢?看看下面的代碼,花一分鐘時(shí)間來(lái)理解它。
- function createEvenNumbersIterator() {
 - let value = 0;
 - return {
 - [Symbol.iterator]() {
 - return this;
 - },
 - next() {
 - value += 2;
 - return { value, done: false };
 - }
 - }
 - }
 - function map(fn, iterable) {
 - const iter = iterable[Symbol.iterator]();
 - return {
 - [Symbol.iterator]() {
 - return this;
 - },
 - next() {
 - const n = iter.next();
 - if (!n.done) {
 - return { value: fn(n.value), done: false };
 - } else {
 - return { done: true };
 - }
 - }
 - }
 - }
 - function filter(fn, iterable) {
 - const iter = iterable[Symbol.iterator]();
 - return {
 - [Symbol.iterator]() {
 - return this;
 - },
 - next() {
 - const n = iter.next();
 - if (!n.done) {
 - if (fn(n.value)) {
 - return { value: n.value, done: false };
 - } else {
 - return this.next();
 - }
 - } else {
 - return { done: true };
 - }
 - }
 - }
 - }
 - function take(n, iterable) {
 - const iter = iterable[Symbol.iterator]();
 - return {
 - [Symbol.iterator]() {
 - return this;
 - },
 - next() {
 - if (n > 0) {
 - n--;
 - return iter.next();
 - } else {
 - return { done: true };
 - }
 - }
 - }
 - }
 - function cycle(iterable) {
 - const iter = iterable[Symbol.iterator]();
 - const saved = [];
 - let idx = 0;
 - return {
 - [Symbol.iterator]() {
 - return this;
 - },
 - next() {
 - const n = iter.next();
 - if (!n.done) {
 - saved[idx++] = n.value;
 - return { value: n.value, done: false };
 - } else {
 - return { value: saved[idx++ % saved.length], done: false };
 - }
 - }
 - }
 - }
 - function collect(iterable) {
 - // consumes the iterator
 - return Array.from(iterable);
 - }
 - const evenNumbersIterator = createEvenNumbersIterator();
 - const result = collect( // 7. and collect the result
 - filter( // ⬆️ 6. keep only values higher than 1
 - val => val > 1, map( // ⬆️ 5. divide obtained values by 2
 - val => val / 2, take( // ⬆️ 4. take only six of them
 - 6, cycle( // ⬆️ 3. make an infinite cycling sequence of them
 - take( // ⬆️ 2. take just three of them
 - 3, evenNumbersIterator // ⬆️ 1. infinite sequence of even numbers
 - )
 - )
 - )
 - )
 - )
 - );
 - console.log(result);
 
這是一大堆代碼,很快我們將看到如何使用生成器和函數(shù)式編程概念來(lái)重構(gòu)所有這些內(nèi)容。保持關(guān)注,并注意我的后續(xù)文章,我們?nèi)匀挥泻芏鄡?nèi)容要講。
作者:MelkorNemesis 譯者:前端小智 來(lái)源:medium
原文:https://medium.com/@MelrNemesis/javascript-lazy-evaluation-iterables-iterators-e0770a5de96f
本文轉(zhuǎn)載自微信公眾號(hào)「 大遷世界」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 大遷世界公眾號(hào)。




















 
 
 










 
 
 
 