泛型数据结构
泛型数据结构需要有通用的处理方式,迭代器模式提供了统一的数据处理接口,从而更好的处理泛型数据结构。
什么是泛型数据结构
泛型数据结构是指可以存储不同类型数据的数据结构,例如数组、链表、集合、映射等。这些数据结构通过泛型参数来指定存储的元素类型,使得同一个数据结构可以处理不同类型的数据。
// 不同类型的数组
const numbers: number[] = [1, 2, 3]
const strings: string[] = ['a', 'b', 'c']
const users: User[] = [{ name: 'Alice' }, { name: 'Bob' }]
// 泛型集合
class Set<T> {
private items: T[] = []
add(item: T): void {
this.items.push(item)
}
}
为什么需要通用的处理方式
不同的数据结构有不同的内部实现和访问方式。如果没有统一的接口,我们需要为每种数据结构编写特定的处理代码:
// 没有统一接口的问题
function processArray<T>(arr: T[]): void {
for (let i = 0; i < arr.length; i++) {
console.log(arr[i])
}
}
function processSet<T>(set: Set<T>): void {
// Set 没有索引访问,需要不同的处理方式
for (const item of set) {
console.log(item)
}
}
function processMap<K, V>(map: Map<K, V>): void {
// Map 需要遍历键值对,又是另一种方式
for (const [key, value] of map) {
console.log(key, value)
}
}
这种方式的问题在于: - 每种数据结构都需要单独的处理逻辑 - 代码重复,难以维护 - 无法编写通用的算法函数
迭代器模式:统一的数据处理接口
迭代器模式提供了一种统一的方式来遍历不同的数据结构,无论其内部实现如何。在 TypeScript 中,迭代器模式通过 Iterable 接口和 Iterator 接口实现。
Iterable 接口
实现了 Iterable 接口的对象可以被 for...of 循环遍历:
interface Iterable<T> {
[Symbol.iterator](): Iterator<T>
}
Iterator 接口
Iterator 接口定义了遍历数据的方式:
interface Iterator<T> {
next(): IteratorResult<T>
}
interface IteratorResult<T> {
value: T
done: boolean
}
TypeScript 中的迭代器实现
数组的迭代器
数组天然实现了 Iterable 接口:
const arr = [1, 2, 3]
// 使用 for...of 遍历
for (const item of arr) {
console.log(item) // 1, 2, 3
}
// 手动使用迭代器
const iterator = arr[Symbol.iterator]()
console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log(iterator.next()) // { value: 3, done: false }
console.log(iterator.next()) // { value: undefined, done: true }
自定义迭代器
我们可以为自定义的数据结构实现迭代器:
class NumberRange implements Iterable<number> {
constructor(
private start: number,
private end: number
) {}
[Symbol.iterator](): Iterator<number> {
let current = this.start
const end = this.end
return {
next(): IteratorResult<number> {
if (current <= end) {
return { value: current++, done: false }
}
return { value: undefined, done: true }
}
}
}
}
// 使用自定义迭代器
const range = new NumberRange(1, 5)
for (const num of range) {
console.log(num) // 1, 2, 3, 4, 5
}
生成器函数实现迭代器
使用生成器函数可以更简洁地实现迭代器:
class NumberRange {
constructor(
private start: number,
private end: number
) {}
*[Symbol.iterator](): Generator<number> {
for (let i = this.start; i <= this.end; i++) {
yield i
}
}
}
const range = new NumberRange(1, 5)
for (const num of range) {
console.log(num) // 1, 2, 3, 4, 5
}
迭代器模式的优势
1. 统一的遍历接口
通过迭代器模式,我们可以用相同的方式处理不同的数据结构:
// 通用的处理函数
function processAll<T>(iterable: Iterable<T>): void {
for (const item of iterable) {
console.log(item)
}
}
// 可以处理任何实现了 Iterable 接口的数据结构
processAll([1, 2, 3]) // 数组
processAll(new Set([1, 2, 3])) // 集合
processAll(new Map([['a', 1], ['b', 2]])) // 映射
processAll(new NumberRange(1, 5)) // 自定义数据结构
2. 延迟计算
迭代器可以按需生成值,而不是一次性生成所有值:
function* fibonacci(): Generator<number> {
let a = 0, b = 1
while (true) {
yield a
;[a, b] = [b, a + b]
}
}
const fib = fibonacci()
console.log(fib.next().value) // 0
console.log(fib.next().value) // 1
console.log(fib.next().value) // 1
console.log(fib.next().value) // 2
// 只在需要时才计算下一个值
3. 组合和转换
迭代器可以轻松组合和转换:
// 映射转换
function* map<T, U>(
iterable: Iterable<T>,
fn: (item: T) => U
): Generator<U> {
for (const item of iterable) {
yield fn(item)
}
}
// 过滤
function* filter<T>(
iterable: Iterable<T>,
predicate: (item: T) => boolean
): Generator<T> {
for (const item of iterable) {
if (predicate(item)) {
yield item
}
}
}
// 组合使用
const numbers = [1, 2, 3, 4, 5]
const doubled = map(numbers, x => x * 2)
const evens = filter(doubled, x => x % 2 === 0)
for (const num of evens) {
console.log(num) // 4, 8
}
总结
迭代器模式为泛型数据结构提供了统一的数据处理接口,使得我们可以:
- 统一遍历方式:无论数据结构如何实现,都可以用
for...of循环遍历 - 编写通用算法:可以编写适用于所有可迭代对象的通用函数
- 延迟计算:按需生成值,提高性能
- 组合转换:轻松组合多个迭代器操作
通过迭代器模式,我们可以在保持类型安全的同时,编写更加通用和可复用的代码。