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
是包含 value
和 done
的结果对象。日常代码中实际上应用的大部分都是
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
。调用
return
或 throw
方法后的 Iterable
的下一次 next()
调用,将始终返回 { value: undefined, done: true }
,表示迭代已完成。这两个方法是由迭代器的调用方来使用的,是迭代器一致抽象的一部分,大部分时候不需要关心。
这差不多就是迭代器的全部。
通过暴露「可迭代迭代器」,我们可以通过
for...of
迭代来获取列表数据,并按需转成 Array
、 Map
等数据类型来自消费,而不是直接消费 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>; }
可以看到,生成器的返回值和迭代器并没有太大区别,仅有细微不同:
- 生成器的
return
和throw
方法是 Required 的。
- 生成器是「可迭代迭代器」,而不是单纯的迭代器。
感谢阅读!
Loading Comments...