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中文网其他相关文章!