Skip to content
微信公众号

迭代器模式

介绍

用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。

for 循环不是迭代器模式

简单的 for 循环并不是迭代器模式,因为 for 循环需要知道对象的内部结构。

如下面的例子

  • 要知道数组的长度
  • 要知道通过 arr[i] 形式来得到 item
js
const arr = [10, 20, 30]
const length = arr.length
for (let i = 0; i < length; i++) {
    console.log(arr[i])
}

简易迭代器

有些对象,并不知道他的内部结构

  • 不知道长度
  • 不知道如何获取 item
js
const pList = document.querySelectorAll('p')
pList.forEach(p => console.log(p))

forEach 就是最建议的迭代器

演示

ts
class DataIterator {
    private data: number[]
    private index = 0

    constructor(container: DataContainer) {
        this.data = container.data
    }

    next(): number | null {
        if (this.hasNext()) {
            return this.data[this.index++]
        }
        return null
    }

    hasNext() {
        if (this.index >= this.data.length) return false
        return true
    }
}

class DataContainer {
    data: number[] = [10, 20, 30, 40]
    getIterator() {
        return new DataIterator(this)
    }
}

const container = new DataContainer()
const iterator = container.getIterator()
while(iterator.hasNext()) {
    const num = iterator.next()
    console.log(num)
}

是否符合设计原则?

5 大设计原则中,最重要的就是:开放封闭原则,对扩展开放,对修改封闭

  • 使用者和目标数据分离,解耦
  • 目标数据自行控制内部迭代逻辑
  • 使用者不关心目标数据的内部结构

场景

JS 有序对象,都内置迭代器

  • 字符串
  • 数组
  • NodeList 等 DOM 集合
  • Map
  • Set
  • arguments

【注意】对象 object 不是有序结构

Symbol.iterator

每个有序对象,都内置了 Symbol.iterator 属性,属性值是一个函数。 执行该函数讲返回 iterator 迭代器,有 next() 方法,执行返回 { value, done } 结构。

js
// 拿数组举例,其他类型也一样

const arr = [10, 20, 30]
const iterator = arr[Symbol.iterator]()

iterator.next() // {value: 10, done: false}
iterator.next() // {value: 20, done: false}
iterator.next() // {value: 30, done: false}
iterator.next() // {value: undefined, done: true}

另外,有些对象的 API 也会生成有序对象

js
const map = new Map([ ['k1', 'v1'], ['k2', 'v2'] ])
const mapIterator = map[Symbol.iterator]()

const values = map.values() // 并不是 Array
const valuesIterator = values[Symbol.iterator]()

// 还有 keys entries

自定义迭代器

ts
interface IteratorRes {
    value: number | undefined
    done: boolean
}

class CustomIterator {
    private length = 3
    private index = 0

    next(): IteratorRes {
        this.index++
        if (this.index <= this.length) {
            return { value: this.index, done: false }
        }
        return { value: undefined, done: true }
    }

    [Symbol.iterator]() {
        return this
    }
}

const iterator = new CustomIterator()
console.log( iterator.next() )
console.log( iterator.next() )
console.log( iterator.next() )
console.log( iterator.next() )

有序结构的作用

for...of

所有有序结构,都支持 for...of 语法

数组操作

数组解构

js
const [node1, node2] = someDomList

扩展操作符

js
const arr = [...someDomList]

Array.from()

js
const arr = Array.form(someDomList)

创建 Map 和 Set

js
const map = new Map([
    ['k1', 'v1'],
    ['k2', 'v2']
])

const set = new Set(someDomList)

Promise.all 和 Promise.race

js
Promise.all([promise1, promise2, promise3])
Promise.race([promise1, promise2, promise3])

Generator

迭代器和生成器,两者密不可分

基本使用

js
function* genNums() {
    yield 10
    yield 20
    yield 30
}

const numsIterator = genNums()
numsIterator.next() // {value: 10, done: false}
numsIterator.next() // {value: 20, done: false}
numsIterator.next() // {value: 30, done: false}
numsIterator.next() // {value: undefined, done: true}

// for (let n of numsIterator) {
//     console.log(n)
// }

yield* 语法

上一节说过,有序结构可用于 yield*

js
function* genNums() {
    yield* [100, 200, 300] // 相当于:循环数组,分别 yield
}
const numsIterator = genNums()
numsIterator.next() // {value: 100, done: false}
numsIterator.next() // {value: 200, done: false}
numsIterator.next() // {value: 300, done: false}
numsIterator.next() // {value: undefined, done: true}

// for (let n of numsIterator) {
//     console.log(n)
// }

最简单的自定义迭代器

js
class CustomIterator {
    private data: number[]

    constructor() {
        this.data = [10, 20, 30]
    }

    * [Symbol.iterator]() {
        yield* this.data
    }
}

const iterator = new CustomIterator()
for (let n of iterator) {
    console.log(n)
}

yield 遍历 DOM 树

有助于深入理解 Generator

js
function* traverse(elemList: Element[]): any {
    for (const elem of elemList) {
        yield elem

        const children = Array.from(elem.children)
        if (children.length) {
            yield* traverse(children)
        }
    }
}

const container = document.getElementById('container')
if (container) {
    for (let node of traverse([container])) {
        console.log(node)
    }
}

本站总访问量次,本站总访客数人次
Released under the MIT License.