一、关于迭代器的问题
在学习迭代器的过程中学习到了这样一个知识点:
因为每个迭代器也实现了Iterable接口 , 所以它们也可以用在任何期待可迭代对象的地方 (例如for-of)
1.迭代器的基本概念
可能有些人看到上面的这句话会有些懵 , 我先将相关的知识简单介绍一下:
1.什么是迭代器?
答: 迭代器 是一种跨类型的通用迭代方法。简单来说就是不论数据是什么类型,只要符合迭代器协议,就可以通过迭代器进行迭代。
2.怎样的数据符合可迭代协议?
答:可迭代协议就是Iterable接口,因此只要实现了Iterable接口的数据类型就符合可迭代协议。
3.怎样实现Iterable接口?
答:必须要有一个键名为
symbol.iterator
的方法,这个方法是一个迭代器工厂,调用它必需返回一个迭代器对象。像这样实现了Iterable接口的,就被称为可迭代对象。4.可迭代对象与for-of的关系
答:有些原生的语言结构,可以接收可迭代对象,自动调用可迭代对象上的Iterable接口。例如,for-of就可以接受一个可迭代对象,循环调用它所关联的迭代器。
5.迭代器对象如何定义?
答:迭代器对象上必须要有一个
next
方法,当调用next
方法时迭代器关联的可迭代对象将会被迭代一次,next
方法会返回一个teratorResult
对象,这个对象包含两个属性:属性value
是当前迭代的值;属性done
表示是否可以通过再次调用next
获取下一个值。
2.自定义一个可迭代对象
根据上面所讲的基础知识,我们就可以自定义一个可迭代对象:
JavaScript
class Counter {
constructor(limit) {
this.limit = limit
}
[Symbol.iterator]() {
let count = 1,
limit = this.limit
return {
next() {
if (count <= limit) {
return { done: false, value: count++ }
} else {
return { done: true, value: undefined }
}
},
}
}
}
let counter = new Counter(3)
for (let i of counter) {
console.log(i)
}
// 1
// 2
// 3
可以看到我创建了一个Counter
类型,它具有[Symbol.iterator]
方法 , 所以它的实例就是可迭代对象 , 可以被for-of消费。
3.迭代器也是可迭代对象?
回到开头的那个知识点,这个知识点的意思其实就是:"迭代器也是一个可迭代对象"。
JavaScript
const arr = [1, 2, 3]
const iter = arr[Symbol.iterator]()
console.log(iter) //Object [Array Iterator] {}
for (const i of iter) {
console.log(i);
}
// 1
// 2
// 3
从上面的例子中就可以看到,数组就是符合这一规律的,数组所关联的迭代器是一个可迭代对象。
当我学到那个地方的时候就十分好奇我自定义的可迭代对象上的迭代器是否符合这个规律呢?于是我进行了测试
JavaScript
let counter = new Counter(3)
const iterator = counter[Symbol.iterator]()
for (const i of iterator) {
console.log(i);
}

报错信息明明白白的告诉我,我手搓的这个手工迭代器不是一个可迭代对象😅
很显然JS内置的这些迭代器 (例如数组关联的迭代器) 与我自定义的迭代器肯定是有差异的,这种差异我暂时还无法搞清楚。

二、实现自引用迭代器
1.来自生成器的灵感
之后在学习生成器的过程中 , 我看到了这样一句话:
生成器对象实现了Iterable接口,它们默认的迭代器是自引用的
同时还给出了如下示例
JavaScript
function* generatorFn() {}
console.log(generatorFn);
// f* generatorFn() {}
console.log(generatorFn()[Symbol.iterator]);
// f [Symbol.iterator]() {native code}
console.log(generatorFn());
// generatorFn {<suspended>}
console.log(generatorFn()[Symbol.iterator]());
// generatorFn {<suspended>}
const g = generatorFn();
console.log(g === g[Symbol.iterator]());
// true
此时我萌生了一个想法:"是不是我自定义的迭代器与JS内置迭代器的差异就是自引用"。于是我又进行了一个测试:
JavaScript
const arr = [1, 2, 3];
const iter = arr[Symbol.iterator]();
console.log(iter);//Object [Array Iterator] {}
const i = iter[Symbol.iterator]();
console.log(i);//Object [Array Iterator] {}
console.log(i[Symbol.iterator]());//Object [Array Iterator] {}
console.log(iter == i);//true
console.log(iter == i[Symbol.iterator]());//true
可以看到不仅数组的迭代器是可迭代对象,而其迭代器的迭代器也是可迭代对象,依次类推都是一样的。 并且这些迭代器其实都是同一个 , 这应该就是所谓的自引用 。
2.结论
基于之前的分析我最终给能够实现自引用的迭代器明确了定义,并升级了我自定义的迭代器:
自引用迭代器,是指迭代器对象上也实现了Iterable接口,并且这个接口返回的就是迭代器本身
JavaScript
class Iterable {
constructor(limit) {
this.limit = limit;
}
[Symbol.]() {
let count = 0,
limit = this.limit;
return {
next() {
if (count < limit) {
return { value: count++, done: false };
} else {
return { value: undefined, done: true };
}
},
[Symbol.iterator]() {
return this;
},
};
}
}
const iterable1 = new Iterable(5)
const iter1 = iterable1[Symbol.iterator]()
const iter2 = iter1[Symbol.iterator]()
console.log(iter1 == iter2);//true