在 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
面板里面直接运行看效果。
迭代器是一个非常好的封装工具,可以抽象出很多逻辑,比如可以和快照模式结合使用,加个内存状态实现幂等,也可以把经常使用的多层循环判断封装起来简化代码等等。