国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

生成器與迭代器

channg / 2206人閱讀

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

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

生成器函數(shù)

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

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

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

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

迭代器

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

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

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

for...of 是遍歷整個迭代器的簡單方式。

生成器的用處

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

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

range(0, Infinity) 是允許的,因為生成器沒生成一個值就會暫停執(zhí)行,所以不會造成死循環(huán),可以由調(diào)用者選擇何時停止。

注意此時不能使用 for...of,因為迭代器永遠(yuǎn)不會 done

計算每一項耗時較長

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

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

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

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

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

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

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

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

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

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

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

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

(1)中的result也是迭代器,在這一步所有函數(shù)都沒有真正開始執(zhí)行,因為沒有任何代碼問他們索要值。

(4)中的擴展運算符對迭代器 result 進行了求值,生成器真正開始執(zhí)行。

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

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

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

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

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

(3)range 輸出第二個值 2

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

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

(4)檢測result還沒有結(jié)束(done為false),問result索要下一個值。

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

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

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

(4)中解構(gòu)運算符檢測到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ù)中“跳來跳去”的執(zhí)行順序也就是這么來的。跟遞歸函數(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)接受直至有瀏覽器實現(xiàn)實在是遙遙無期,有沒有其他辦法呢?

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

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

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

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

可以看到,生成器返回的迭代器對象就好像是被生成器 new 出來的一樣(但是生成器不是構(gòu)造函數(shù)不能被 new)。但是總之我們可以通過給生成器函數(shù)的 prototype 添加方法實現(xiàn)給迭代器添加方法的效果。實現(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)時(dan)間(teng)使用迭代器實現(xiàn)了幾乎所有 ES7 中 Array 的成員方法和靜態(tài)方法,廣而告之,歡迎來噴:https://github.com/CarterLi/q...

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

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/103737.html

相關(guān)文章

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

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

    王軍 評論0 收藏0
  • Python:range 對象并不是迭代

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

    draveness 評論0 收藏0
  • 深入理解ES6之《迭代生成

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

    王軍 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<