关于 Javascript 迭代器的 Review

关于 Javascript 迭代器的 Review

Tags
Javascript
Code Complete2
Published
Nov 13, 2023
Note: 这里仅讨论同步迭代器,异步迭代器暂时不讨论

起因

最近在看 Code Complete 2。
看到 信息隐藏(Information Hiding) 时,发现自己之前写的 Class,如果需要暴露一个列表数据出去,使用的都是 Array ,然后数据的消费者可以使用 Array 上面的函数式方法来很方便的迭代。
但是这样就和 隐藏变化源 这一原则相冲突。
如果需要变更为不支持索引访问的数据结构,比如树和链表,则会造成较大的破坏性更改。
 
实际上,我们的需求仅仅是获取列表数据,而不是暴露 Array 上面的方法或者特性。
使用迭代器,可以做到隐藏变化源。这也是大部分比较新的现代语言都具备的特性(Python、Rust、C#)。
 
迭代器仅仅提供用于迭代的 next() 方法,而不会暴露任何 Array.prototype 上面无用的方法和 Array 本身的特性出去,很好的做到了一致抽象和信息隐藏。
 
通过迭代器,我们可以将 update(index, value) 的语义,从「更新数组 index 位置的 value」,变成「以 0 为第一项,更新 index 项位置的 value」,将直觉上的 O(1) 语义转化为 O(n) 语义。
当然,这是通用意义上的设计。如果有特殊的需求,也没必要非得使用迭代器抽象。
 

辨析和总结

这里不介绍基本的语法概念,仅仅做总结和辨析,也会梳理类型,以便在 Typescript 中声明。
 
迭代器相关的类型声明大致如下:
interface Iterator<T, TReturn = any, TNext = undefined> { // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places. next(...args: [] | [TNext]): IteratorResult<T, TReturn>; return?(value?: TReturn): IteratorResult<T, TReturn>; throw?(e?: any): IteratorResult<T, TReturn>; } interface Iterable<T> { [Symbol.iterator](): Iterator<T>; } interface IterableIterator<T> extends Iterator<T> { [Symbol.iterator](): IterableIterator<T>; } interface IteratorResult<TReturn> { done: true; value: TReturn; }
 
Iterator 是「迭代器」。
Iterable 是「可迭代对象」。
Iterable 是实现了 [Symbol.iterator]() 方法的对象,调用 [Symbol.iterator]() 会返回一个 Iterator
也就是说,默认情况下,Iterator 并不是可迭代的。Iterable 上也没有 next() 方法。
IterableIterator 是「可迭代迭代器」。它是自实现了 [Symbol.iterator]()Iterator ,既是 Iterable ,又是 Iterator
IteratorResult 是包含 valuedone 的结果对象。
 
日常代码中实际上应用的大部分都是 IterableIterator ,如 Map.prototype.entries()Array.prototype.entries() 等。
我们在 Typescript 中声明的,暴露给外部的也应该是 IterableIterator
Object 是一个例外, Object 上的任何方法,如 values()keys()entries() 返回的都是纯 Array ,而不是可迭代迭代器。
 
通过类型可以看到,迭代器仅 next() 方法是必须的,其余的是可选的。
return(value) 会返回一个自定义结果,而不是 next() 的结果,并将 Iterable 标记为 done: true
throw(e) 会抛出一个同步错误,error 为传入的参数,并将 Iterable 标记为 done: true
调用 returnthrow 方法后的 Iterable 的下一次 next() 调用,将始终返回 { value: undefined, done: true } ,表示迭代已完成。
这两个方法是由迭代器的调用方来使用的,是迭代器一致抽象的一部分,大部分时候不需要关心。
 
这差不多就是迭代器的全部。
通过暴露「可迭代迭代器」,我们可以通过 for...of 迭代来获取列表数据,并按需转成 ArrayMap 等数据类型来自消费,而不是直接消费 Class。
 

关于生成器(Generator)

interface Generator<T = unknown, TReturn = any, TNext = unknown> extends Iterator<T, TReturn, TNext> { // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places. next(...args: [] | [TNext]): IteratorResult<T, TReturn>; return(value: TReturn): IteratorResult<T, TReturn>; throw(e: any): IteratorResult<T, TReturn>; [Symbol.iterator](): Generator<T, TReturn, TNext>; }
 
可以看到,生成器的返回值和迭代器并没有太大区别,仅有细微不同:
  1. 生成器的 returnthrow 方法是 Required 的。
  1. 生成器是「可迭代迭代器」,而不是单纯的迭代器。
 
 
感谢阅读!
notion image
notion image

Loading Comments...