02-迭代协议
12.1 迭代协议
迭代器本身并不那么有趣,但它却可以支持那些有趣的行为。迭代器协议让任何对象变得可迭代。试想一下,如果你准备创建一个logging类,并且把时间戳作为附加消息。在logging类内部,使用了一个数组来存储时间戳消息:
class Log {
constructor() {
this.messages = [];
}
add(message) {
this.messages.push({ message, timestamp: Date.now() });
}
}
看起来还不错……但是如果想对log记录进行迭代呢?当然可以这么做,只要访问 log.messages 就行,不过如果log可以像数组一样直接迭代,不是更好吗?迭代器协议就可以实现这个。迭代器协议是说,如果一个类提供了一个符号方法 Symbol.iterator ,这个方法返回一个具有迭代行为的对象(比如:对象有next方法,同时next方法返回一个包含 value 和 done 的对象),那么这个类就是可迭代的!修改Log类,给它添加一个 Symbol.iterator 方法吧:
class Log {
constructor() {
this.messages = [];
}
add(message) {
this.messages.push({ message, timestamp: Date.now() });
}
[Symbol.iterator]() {
return this.messages.values();
}
}
下面可以像数组那样迭代 Log 类的实例了:
const log = new Log();
log.add("first day at sea");
log.add("spotted whale");
log.add("spotted another vessel");
//...
// 像数组一样迭代log !
for(let entry of log) {
console.log(`${entry.message} @ ${entry.timestamp}`);
}
在这个例子中,通过从 messages 数组中取出一个迭代器的方式保持迭代器协议,当然也可以编写自己的迭代器:
class Log {
//...
[Symbol.iterator]() {
let i = 0;
const messages = this.messages;
return {
next() {
if(i >= messages.length)
return { value: undefined, done: true };
return { value: messages[i++], done: false };
}
}
}
}
至止,以上所使用的例子都是迭代预先定义好元素个数的数组:一本书的页数,或者log中的日期消息记录。其实,迭代器还可以用来表示那些含有无穷值的对象。
为了演示这个,大家来看一个很简单的例子:菲波那切数列。确切地说,菲波那切数列不难生成,但是它们却依赖之前的数字。对于外行人来说,菲波那切数列就是前两个数字的和。这个序列从1和1开始,下一个数字是1+1,也就是2。再下一个数字是1+2,也就是3。第四个数字是2+3,也就是5,以此类推。序列看起来是这样的:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,...
菲波那切数列永远不会停止。同样,应用程序也不可能知道需要多少元素,这就促使它成为了一个使用迭代器的理想应用。这个例子跟之前的例子唯一不同的地方在于,它的done值永远不会是true:
class FibonacciSequence {
[Symbol.iterator]() {
let a = 0, b = 1;
return {
next() {
let rval = { value: b, done: false };
b += a;
a = rval.value;
return rval;
}
};
}
}
如果将 for…of 循环用在 FibonacciSequence 的一个实例中,就会得到一个无限循环…菲波那切数列永远都用不完!为了防止这个发生,在循环了10个元素后加一个break语句。
const fib = new FibonacciSequence();
let i = 0;
for(let n of fib) {
console.log(n);
if(++i > 9) break;
}