成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

生成器與迭代器

channg / 2221人閱讀

摘要:我們前面說(shuō)過(guò)生成器函數(shù)本身永遠(yuǎn)返回一個(gè)迭代器,而生成器中的語(yǔ)句實(shí)際上是關(guān)閉迭代器的標(biāo)志,實(shí)際代表。生成器函數(shù)返回的迭代器本身就是一個(gè)對(duì)象,很容易想到改變對(duì)象的原型實(shí)現(xiàn)。

之前的文章 寫(xiě)到了 Generator 與異步編程的關(guān)系,其實(shí)簡(jiǎn)化異步編程只是 Generator 的“副業(yè)”,Generator 本身卻不是為異步編程而存在。

生成器函數(shù)

我們看 Generator 自身的含義——生成器,就是產(chǎn)生序列用的。比如有如下函數(shù):

function* range(start, stop) {
  for (let item = start; item < stop; ++item) {
    yield item;
  }
}

range 就是一個(gè)生成器函數(shù),它自身是函數(shù)可以調(diào)用(typeof range === "function" // true),但又與普通函數(shù)不同,生成器函數(shù)(GeneratorFunction)永遠(yuǎn)返回一個(gè)生成器(Generator)

注:我們通常所說(shuō)的 Generator 實(shí)際上指生成器函數(shù)(GeneratorFunction),而把生成器函數(shù)返回的對(duì)象稱(chēng)作迭代器(Iterator)。由于感覺(jué)“生成器函數(shù)”返回“生成器”這句話(huà)有些拗口,下文沿用生成器和迭代器的說(shuō)法。

迭代器

初次調(diào)用生成器實(shí)際上不執(zhí)行生成器函數(shù)的函數(shù)體,它只是返回一個(gè)迭代器,當(dāng)用戶(hù)調(diào)用迭代器的 next 函數(shù)時(shí),程序才開(kāi)始真正執(zhí)行生成器的函數(shù)體。當(dāng)程序運(yùn)行到 yield 表達(dá)式時(shí),會(huì)將 yield 后面表達(dá)式的值作為 next 函數(shù)的返回值(的一部分)返回,函數(shù)本身暫停執(zhí)行。

const iterator = range(0, 10); // 獲取迭代器
const value1 = iterator.next().value; // 獲取第一個(gè)值 => 0
const value2 = iterator.next().value; // 獲取第二個(gè)值 => 1

next 返回值是一個(gè)對(duì)象,包含兩個(gè)屬性 valuedone。value 即為 yield 后面表達(dá)式的值,done 表示函數(shù)是否已經(jīng)結(jié)束(return)。如果函數(shù) return(或執(zhí)行到函數(shù)尾退出,相當(dāng)于 return undefined),則 done 為 true,value 為 return 的值。

for...of 是遍歷整個(gè)迭代器的簡(jiǎn)單方式。

生成器的用處

上面說(shuō)到,生成器就是生成序列用的。但是與直接返回?cái)?shù)組不同,生成器返回序列是一項(xiàng)一項(xiàng)計(jì)算并返回的,而返回?cái)?shù)組總是需要計(jì)算出所有值后統(tǒng)一返回。所以至少有三種情況應(yīng)該考慮使用生成器。

序列有無(wú)限多項(xiàng),或者調(diào)用者不確定需要多少項(xiàng)

range(0, Infinity) 是允許的,因?yàn)樯善鳑](méi)生成一個(gè)值就會(huì)暫停執(zhí)行,所以不會(huì)造成死循環(huán),可以由調(diào)用者選擇何時(shí)停止。

注意此時(shí)不能使用 for...of,因?yàn)榈饔肋h(yuǎn)不會(huì) done

計(jì)算每一項(xiàng)耗時(shí)較長(zhǎng)

如果計(jì)算一項(xiàng)的值需要 1ms,那么計(jì)算 1000 項(xiàng)就需要 1s,如果不將這 1s 拆分,就會(huì)導(dǎo)致瀏覽器卡頓甚至假死。這時(shí)可以用生成器每生成幾項(xiàng)就將控制權(quán)交還給瀏覽器,用于響應(yīng)用戶(hù)事件,提升用戶(hù)體驗(yàn)(當(dāng)然這里更有效的方法是將代碼放到 Web Worker 里執(zhí)行)

節(jié)省內(nèi)存

如果序列很長(zhǎng),直接返回?cái)?shù)組會(huì)占用較大內(nèi)存。生成器返回值是一項(xiàng)一項(xiàng)返回,不會(huì)一次性占用大量?jī)?nèi)存(當(dāng)然生成器為了保留執(zhí)行上下文比通常函數(shù)占用內(nèi)存更多,但是它是個(gè)定值,不隨迭代次數(shù)增加)

使用生成器實(shí)現(xiàn)懶加載的 map、filter

Array#map、Array#filter 是 ES5 引入的(絕對(duì)不算新的)兩個(gè)非常常用的函數(shù),前者將數(shù)組每一項(xiàng)通過(guò)回調(diào)函數(shù)映射到新數(shù)組(值變量不變),后者通過(guò)回調(diào)函數(shù)過(guò)濾某些不需要的項(xiàng)(量變值不變),他們都會(huì)生成新的數(shù)組對(duì)象,如果數(shù)組本身較長(zhǎng)或者寫(xiě)了很長(zhǎng)的 map、filter 調(diào)用鏈,就可能造成內(nèi)存浪費(fèi)。

這時(shí)就可以考慮使用生成器實(shí)現(xiàn)這兩個(gè)函數(shù),非常簡(jiǎn)單:

function* map(iterable, callback) {
  let i = 0;
  for (const item of iterable) {         // 遍歷數(shù)組
    yield callback(item, i++, iterable); // 獲取其中一項(xiàng),調(diào)用回調(diào)函數(shù),yield 返回值
  }
}

function* filter(iterable, callback) {
  let i = 0;
  for (const item of iterable) {         // 遍歷數(shù)組
    if (callback(item, i++, iterable)) { // 獲取其中一項(xiàng),調(diào)用回調(diào)函數(shù)
      yield item;                        // 僅當(dāng)回調(diào)函數(shù)返回真時(shí),才 yield 值
    }
  }
}

可以看到我在代碼中寫(xiě)的是“可迭代的”(iterable),而不限于數(shù)組(由于實(shí)現(xiàn)了 Symbol.iterator 所以數(shù)組也是可迭代對(duì)象)。比如可以這么用:

const result = map(     // (1)
  filter(               // (2)
    range(1, 10000),    // (3)
    x => x % 2 === 0,
  ),
  x => x / 2,
)
console.log(...result); // (4)

注意,程序在解構(gòu)運(yùn)算符 ...result 這一步才真正開(kāi)始計(jì)算 result 的值(所謂的懶加載),而且它的值也是一個(gè)一個(gè)計(jì)算的:

(3)生成迭代器,提供值給(2);(2)提供值給(1)

(1)中的result也是迭代器,在這一步所有函數(shù)都沒(méi)有真正開(kāi)始執(zhí)行,因?yàn)闆](méi)有任何代碼問(wèn)他們索要值。

(4)中的擴(kuò)展運(yùn)算符對(duì)迭代器 result 進(jìn)行了求值,生成器真正開(kāi)始執(zhí)行。

result 的值來(lái)自于 (1),于是(1)首先開(kāi)始執(zhí)行。

(1)中map函數(shù)使用 for...of 遍歷(2)提供的迭代器,于是(2)開(kāi)始執(zhí)行

(2)中filter函數(shù)使用 for...of 遍歷(3)提供的迭代器,于是(3)開(kāi)始執(zhí)行

(3)中range函數(shù)開(kāi)始執(zhí)行,循環(huán)得到第一個(gè)值 1。遇到 yield 關(guān)鍵字,將值 1 輸出給(2)

(2)中的 for...of 獲得一個(gè)值 1,執(zhí)行函數(shù)體。callback 返回 false,忽略之?;氐?for...of,繼續(xù)問(wèn)(3)索要下一個(gè)值

(3)range 輸出第二個(gè)值 2

(2)中的 for...of 獲得一個(gè)值 2,執(zhí)行函數(shù)體。callback 返回 true,將值 2 輸出給 (1)

(1)中的 for...of 獲得一個(gè)值 2,執(zhí)行函數(shù)體得到 1。將值 1 輸出給(4),console.log 獲得第一個(gè)參數(shù)

(4)檢測(cè)result還沒(méi)有結(jié)束(done為false),問(wèn)result索要下一個(gè)值。

回到第 4 步循環(huán),直至(3)中的循環(huán)結(jié)束函數(shù)體退出為止,(3)返回的迭代器被關(guān)閉

(2)中 for...of 檢測(cè)到迭代器已被關(guān)閉(done為true),循環(huán)結(jié)束,函數(shù)退出,(2)返回的迭代器被關(guān)閉

同理(1)返回的迭代器被關(guān)閉

(4)中解構(gòu)運(yùn)算符檢測(cè)到result已關(guān)閉,結(jié)構(gòu)結(jié)束。將結(jié)構(gòu)得到的所有值作為 console.log 的參數(shù)列表輸出

總結(jié)一下,代碼執(zhí)行順序大概是這樣:(3) -> (2) -> (1) -> (4) -> (1) -> (2) -> (3) -> (2) -> (3) -> (2) -> (1) -> (4) -> (1) -> (2) -> (3) -> ……

是不是相當(dāng)復(fù)雜?異步函數(shù)中“跳來(lái)跳去”的執(zhí)行順序也就是這么來(lái)的。跟遞歸函數(shù)一樣,不要太糾結(jié)生成器函數(shù)的執(zhí)行順序,而要著重理解它這一步究竟要做什么事情。

生成器函數(shù)的鏈?zhǔn)秸{(diào)用

這樣的代碼 map(filter(range(1, 100), x => x % 2 === 0), x => x / 2) 似乎有些d疼,好像又看到了被回調(diào)函數(shù)支配的恐懼。雖然有人提出了管道符的提議,但這種 stage 1 的提議被標(biāo)準(zhǔn)接受直至有瀏覽器實(shí)現(xiàn)實(shí)在是遙遙無(wú)期,有沒(méi)有其他辦法呢?

普通函數(shù)可以簡(jiǎn)單的通過(guò)在類(lèi)中返回 this 實(shí)現(xiàn)函數(shù)的鏈?zhǔn)秸{(diào)用(例如經(jīng)典的 jQuery),但是這點(diǎn)在生成器中不適用。我們前面說(shuō)過(guò)生成器函數(shù)本身永遠(yuǎn)返回一個(gè)迭代器,而生成器中的 return 語(yǔ)句實(shí)際上是關(guān)閉迭代器的標(biāo)志,return this 實(shí)際代表 { value: this, done: true }。生成器中的 return 和普通函數(shù)用法相近但實(shí)際含義大大不同。

鏈?zhǔn)秸{(diào)用需要函數(shù)的返回值是個(gè)對(duì)象,并且對(duì)象中包含可鏈?zhǔn)秸{(diào)用的所有函數(shù)。生成器函數(shù)返回的迭代器本身就是一個(gè)對(duì)象,很容易想到改變對(duì)象的原型實(shí)現(xiàn)。

迭代器有如下原型繼承鏈:

迭代器對(duì)象 -> 生成器.prototype -> 生成器.prototype.prototype(Generator) -> Object.prototype -> null

可以看到,生成器返回的迭代器對(duì)象就好像是被生成器 new 出來(lái)的一樣(但是生成器不是構(gòu)造函數(shù)不能被 new)。但是總之我們可以通過(guò)給生成器函數(shù)的 prototype 添加方法實(shí)現(xiàn)給迭代器添加方法的效果。實(shí)現(xiàn)如下

function* range(start, stop) {
  for (let item = start; item < stop; ++item) {
    yield item;
  }
}

function* map(callback) {
  let i = 0;
  for (const item of this) {
    yield callback(item, i++, this);
  }
}

function* filter(callback) {
  let i = 0;
  for (const item of this) {
    if (callback(item, i++, this)) {
      yield item;
    }
  }
}

[range, map, filter].forEach(x => Object.assign(x.prototype, { range, map, filter }));

// 使用
const result = range(1, 100)
  .filter(x => x % 2 === 0)
  .map(x => x / 2);

console.log(...result);

筆者業(yè)(xian)余(de)時(shí)(dan)間(teng)使用迭代器實(shí)現(xiàn)了幾乎所有 ES7 中 Array 的成員方法和靜態(tài)方法,廣而告之,歡迎來(lái)噴:https://github.com/CarterLi/q...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/103737.html

相關(guān)文章

  • 當(dāng)談?wù)?em>迭代時(shí),我談些什么?

    摘要:示例代碼如下此示例中可以看出,當(dāng)?shù)鹘K止時(shí),通過(guò)拋出異常告知迭代器已耗盡。但如果迭代器所指向的數(shù)據(jù)結(jié)構(gòu)在其存在時(shí)發(fā)生了插入或刪除操作,則迭代器將可能失效。與的情形類(lèi)似,對(duì)進(jìn)行任何插入操作也將損壞迭代器。 花下貓語(yǔ):之前說(shuō)過(guò),我對(duì)于編程語(yǔ)言跟其它學(xué)科的融合非常感興趣,但我還說(shuō)漏了一點(diǎn),就是我對(duì)于 Python 跟其它編程語(yǔ)言的對(duì)比學(xué)習(xí),也很感興趣。所以,我一直希望能聚集一些有其它語(yǔ)言基...

    王軍 評(píng)論0 收藏0
  • Python:range 對(duì)象并不是迭代

    摘要:簡(jiǎn)評(píng)迭代器是惰性可迭代對(duì)象,函數(shù)在中是一個(gè)惰性的可迭代對(duì)象,那么是不是迭代器呢為什么。如果你不能將某些東西傳遞給函數(shù),那么它不是一個(gè)迭代器。的對(duì)象不是迭代器。 簡(jiǎn)評(píng):迭代器(iterator)是惰性可迭代對(duì)象(lazy iterable),range 函數(shù)在 Python 3 中是一個(gè)惰性的可迭代對(duì)象,那么 range 是不是迭代器呢?為什么。 TLNR:Python 3 中的 ran...

    draveness 評(píng)論0 收藏0
  • 深入理解ES6之《迭代生成

    摘要:什么是迭代器中創(chuàng)建迭代器如下所示什么是生成器生成器是一種返回迭代器的函數(shù)每當(dāng)招待完一條語(yǔ)句后函數(shù)就會(huì)自動(dòng)停止執(zhí)行關(guān)鍵字可返回任何值或表達(dá)式關(guān)鍵字只可在生成器內(nèi)部使用,在其它地方使用會(huì)導(dǎo)致程序拋出語(yǔ)法錯(cuò)誤所以下面例子是有錯(cuò)誤的可迭代對(duì)象具有屬 什么是迭代器 ES5中創(chuàng)建迭代器如下所示: function createIterator(items) { var i = 0 retu...

    王軍 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<