X.D 笔记X.D 笔记

在 JavaScript 中使用遍历器 Iterator

在 JavaScript 中使用遍历器 Iterator

迭代器

迭代器,是设计模式中一个相当重要的模式,基本上大部分编程语言都内置了这个模式,通过实现相关接口即可。

ES2015 中,正式定义了迭代器的规范,在 JavaScript 的内置类型中,字符串、数组,Map/Set 都实现了这个规范。

实现迭代器的好处,就可以使用 for of 语法,简化代码

const text = 'xdnote.com';

// 使用循环写法
for (let i = 0; i < text.length; i++) {
    console.log(text[i]);
    // 依次输出 x d n o t e . c o m
}

// 使用迭代器的写法
for (let letter of text) {
    console.log(letter);
    // 依次输出 x d n o t e . c o m
}

而使用迭代器的原生写法时,可以使用 Symbol.iterator 符号,写成这样。

const text = 'xdnote.com';
const it = text[Symbol.iterator]();
let letter;
while (!(letter = it.next()).done) {
    console.log(letter.value);
}

当然,一般情况下不会使用这种写法,但为什么可以这样写,可以看一看 ES2015 的规范:

ES2015中的定义

interface IteratorResult<TReturn> {
    done: boolean;
    value: TReturn;
}

interface Iterator<T, TReturn = any, TNext = undefined> {
    // 下一个值
    next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
    return?(value?: TReturn): IteratorResult<T, TReturn>;
    throw?(e?: any): IteratorResult<T, TReturn>;
}

interface IterableIterator<T> extends Iterator<T> {
    // 返回迭代器
    [Symbol.iterator](): IterableIterator<T>;
}

在规范中,只要实现 next 方法即可,返回对象包括下一个值 value 及是否已完成的状态 done.

另外的两个可选方法中, throw 方法也好理解,遇到错误将会用 throw 抛出。

return 方法主要也是用于循环中中断时触发,用于一些特殊逻辑处理,中断包括三种: break continue throw

自定义迭代器

既然已经知道了定义,那么自己就可以自定义迭代器了。

例如:我需要从1遍历到100,不要5和7的倍数,正常情况下,可能会写一个 1到100 的循环,内部进行判断,判断正常就输出。

但使用迭代器时,可以把判断封装在迭代器之中,使用时直接输出就可以了,关于迭代器的使用,也有类和函数两种方式:

使用面向对象的方式,更直观,通过 class 关键字:

// 在 TypeScript 中,进行类型约束与提示
// 可以加上 implements IterableIterator<number> 
class NumberIterator {
    i = 0;

    getNext() {
        this.i++;
        if (this.i % 7 == 0 || this.i % 5 == 0) this.i++;
        return this.i
    }

    // 这里写死,返回自身
    [Symbol.iterator]() {
        return this;
    }

    // 实现 next 方法
    next() {
        return { value: this.getNext(), done: this.i >= 100 }
    }
}

const it = new NumberIterator();
for (let num of it) {
    // 输出数字 1 - 100,不会输出5和7 的倍数
    console.log(num)
}

使用函数式写法,更简洁优雅, 通过* yield生成器语法实现

function* numberIterator() {
    let current = 1;
    while (current < 100) {
        if (current % 7 == 0 || current % 5 == 0) {
            current++;
            continue;
        }
        yield current++;
    }
}

for (let value of numberIterator()) {
    // 输出数字 1 - 100,不会输出5和7 的倍数
    console.log(value);
}

小结

上面的代码都可以在浏览器工具Console面板里面直接运行看效果。

迭代器是一个非常好的封装工具,可以抽象出很多逻辑,比如可以和快照模式结合使用,加个内存状态实现幂等,也可以把经常使用的多层循环判断封装起来简化代码等等。