Python 擁有許多強大的實用函數,例如 range
、enumerate
、zip
等,這些函數基於可迭代物件和迭代器協定建構。結合生成器函數,這些協議自 2016 年左右起就在所有 Evergreen 瀏覽器和 Node.js 中可用,但在我看來,它們的使用率卻低得令人吃驚。在這篇文章中,我將使用 TypeScript 實作其中一些輔助函數,希望能改變這個現狀。
迭代器協定是一種產生值序列的標準方法。要使一個物件成為迭代器,它必須透過實作 next
方法來遵守迭代器協議,例如:
<code class="language-typescript">const iterator = { i: 0, next() { return { done: false, value: this.i++ }; } };</code>
然後,我們可以重複呼叫 next
方法來取得值:
<code class="language-typescript">console.log(iterator.next().value); // → 0 console.log(iterator.next().value); // → 1 console.log(iterator.next().value); // → 2 console.log(iterator.next().value); // → 3 console.log(iterator.next().value); // → 4</code>
next
方法應該傳回一個對象,該物件包含一個 value
屬性(包含實際值)和一個 done
屬性(指定迭代器是否已耗盡,即是否無法再產生值)。根據 MDN 的說法,這兩個屬性都不是嚴格必需的,如果兩者都缺失,則傳回值被視為 { done: false, value: undefined }
。
可迭代物件協定允許物件定義其自身的迭代行為。要遵守可迭代物件協議,物件必須使用 Symbol.iterator
鍵定義一個方法,該方法傳回一個迭代器。許多內建物件(如 Array
、TypedArray
、Set
和 Map
)都實現了此協議,因此可以使用 for...of
循環對其進行迭代。
例如,對於數組,values
方法被指定為數組的 Symbol.iterator
方法:
<code class="language-typescript">console.log(Array.prototype.values === Array.prototype[Symbol.iterator]); // → true</code>
我們可以結合迭代器和可迭代物件協定來建立一個可迭代的迭代器,如下所示:
<code class="language-typescript">const iterable = { i: 0, [Symbol.iterator]() { const iterable = this; return { next() { return { done: false, value: iterable.i++ }; } }; } };</code>
這兩個協議的名稱不幸地非常相似,至今仍然讓我感到困惑。
正如您可能猜到的那樣,我們的迭代器和可迭代物件範例是無限的,這意味著它們可以永遠生成值。這是一個非常強大的特性,但也容易成為一個陷阱。例如,如果我們要在一個for...of
循環中使用可迭代對象,則循環將永遠持續下去;或者用作Array.from
的參數,JS 最終會拋出一個RangeError
,因為數組會變得太大:
<code class="language-typescript">// 将无限循环: for (const value of iterable) { console.log(value); } // 将抛出 RangeError const arr = Array.from(iterable);</code>
迭代器和可迭代物件甚至可以無限的原因是它們是惰性求值的,即只有在使用時才會產生值。
雖然迭代器和可迭代物件是寶貴的工具,但編寫起來有點麻煩。作為替代方案,引入了生成器函數。
產生器函數使用function*
(或function *
,星號可以在function
關鍵字和函數名稱之間任意位置)指定,允許我們中斷函數的執行,使用yield
關鍵字傳回值,並在稍後繼續中斷的地方繼續執行,同時保持其內部狀態:
<code class="language-typescript">const iterator = { i: 0, next() { return { done: false, value: this.i++ }; } };</code>
如引言中所述,Python 有一些非常有用的內建實用程序,它們基於上述協定。 JavaScript 最近也為迭代器增加了一些輔助方法,例如 .drop()
和 .filter()
,但(也許還沒有)擁有 Python 中一些更有趣的實用程式。
現在理論部分已經結束,讓我們開始實作一些 Python 函數吧!
注意:此處顯示的這些實作都不應原樣用於生產環境。 它們缺乏錯誤處理和邊界條件檢查。
Python 中的 enumerate
為輸入序列或可迭代物件中的每個項目傳回一系列元組,其中第一個位置包含計數,第二個位置包含項目:
<code class="language-typescript">console.log(iterator.next().value); // → 0 console.log(iterator.next().value); // → 1 console.log(iterator.next().value); // → 2 console.log(iterator.next().value); // → 3 console.log(iterator.next().value); // → 4</code>
enumerate
也接受一個可選的 start
參數,指示計數器應從何處開始:
<code class="language-typescript">console.log(Array.prototype.values === Array.prototype[Symbol.iterator]); // → true</code>
讓我們使用生成器函數在 TypeScript 中實現它。我們可以使用 python 文件中概述的實作作為指導
<code class="language-typescript">const iterable = { i: 0, [Symbol.iterator]() { const iterable = this; return { next() { return { done: false, value: iterable.i++ }; } }; } };</code>
由於 JavaScript 中的字串實現了可迭代物件協議,我們可以簡單地將字串傳遞給我們的 enumerate
函數並像這樣調用它:
<code class="language-typescript">// 将无限循环: for (const value of iterable) { console.log(value); } // 将抛出 RangeError const arr = Array.from(iterable);</code>
repeat
是內建 itertools
函式庫的一部分,它重複給定的輸入 elem
n 次,如果未指定 n,則無限重複。我們再次可以將 python 文件中的實作作為起點。
<code class="language-typescript">function* sequence() { let i = 0; while (true) { yield i++; } } const seq = sequence(); console.log(seq.next().value); // → 0; console.log(seq.next().value); // → 1; console.log(seq.next().value); // → 2; // 将无限循环,从 3 开始 for (const value of seq) { console.log(value); }</code>
(此處省略了 cycle
、range
函數的實現,因為篇幅過長,但其邏輯與原文相同,只是將程式碼用 TypeScript 重寫)
這是我的第一篇部落格文章,我希望您覺得它有趣,也許您會在未來的專案中使用迭代器、可迭代物件和生成器。如果您有任何疑問或需要澄清,請留下評論,我很樂意提供更多資訊。
要注意的是,與使用計數器的原始 for
循環相比,效能相差甚遠。這在許多情況下可能無關緊要,但在高性能場景中絕對很重要。當我將 PCM 資料繪製到畫布上並使用迭代器和生成器時,發現幀丟失了,這讓我很苦惱。事後看來這可能是顯而易見的,但當時對我來說卻並非如此 :D
乾杯!
以上是Python 化 JavaScript的詳細內容。更多資訊請關注PHP中文網其他相關文章!