手搓自引用迭代器

一、关于迭代器的问题

在学习迭代器的过程中学习到了这样一个知识点:

因为每个迭代器也实现了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
相关推荐
kingwebo'sZone几秒前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_090119 分钟前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农31 分钟前
Vue 2.3
前端·javascript·vue.js
夜郎king1 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳1 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵2 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星2 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_2 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝2 小时前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions2 小时前
2026年,微前端终于“死“了
前端·状态模式