可迭代还是可遍历?

516 阅读3分钟

迭代器的概念在很多语言里面都有,一些常用的新语法就是基于这个实现的,比如最常用的语法糖async await就是基于promise和生成器,再比如有时候写js代码使用 for of 的时候莫名提示 object is not iterable。其实都是迭代器在作祟。如果你同样遇到过该问题,我希望能够以自身浅薄的理解来解释它。

屏幕截图 2022-09-27 181153.png

疑惑

之前一直有这么几个疑问

  1. 到底什么是迭代器?
  2. 什么是可迭代对象?
  3. 可迭代和可遍历有区别吗?
    基于这些问题来写这篇文章。

主要内容

什么是迭代器?

迭代器只是一段特殊的代码,它希望通过这种方式'人为地'为数据提供一种统一的遍历方式,它并不在乎你的数据类型和结构,它唯一想的就是依次遍历然后拿到里面的数据。下面的代码返回的对象就是一个迭代器。它内部实现了next方法,next方法中又返回对象{value:...,done:...};实现了这种结构的对象在js中就叫做一个迭代器。
不要纠结为什么会是这种结构,就像是纠结为什么1+1===2。这就是定义的一种规范,如果你是设计者你甚至可以返回数组或是别的。

function () {
      return {// 返回迭代器对象
        next() {
          if (...) {//遍历完成
            return {
              value: value,
              done: false,
            };
          } else {//未遍历完成,继续遍历
            return {
              value: undefined,
              done: true,
            };
          }
        },
      };
    };

在曾经对于字符串和数组而言可以使用for循环遍历,对于对象我们可以使用(for... in.. )来遍历,先不说针对不同类型需要记住不同的方式,如果我们需要遍历一个set,map,或者是普通的 1,2,3三个数字。那么通过普通的遍历就很难实现。这个时候迭代器就可以做到。
比如常见的想要遍历对象,把对象的key和value收集起来,再通过迭代器返回。

//遍历Object,一般添加到原型上,所有对象实例都可以使用
Object.prototype[Symbol.iterator] = function () {
      let arr = [];
      let index = 0;
      for (const key in this) { //收集对象的属性名和属性值
        arr.push(this[key]);
      }
      return {
        next() {
          if (index < arr.length) {
            return {
              value: arr[index++],
              done: false,
            };
          } else {
            return {
              value: undefined,
              done: true,
            };
          }
        },
      };
    };
    //测试对象obj1调用for of此时不报错
    const obj1 = {
      name: "zxy",
      age: "18",
    };
    // 输出 zxy ,12
    for (const key of obj1) {
      console.log(key);
    }

哪怕想要迭代数字 1,2,3

function makeIterator(...args) {//生成迭代器
    var i = 0;
    return {
        next() {
            if (i < args.length) {
                return {
                    value: args[i++],
                    done: false,
                }
            } else {
                return {
                    value: undefined,
                    done: true
                }
            }
        }
    }
}

const iterator = makeIterator(1, 2, 3);
console.log(iterator.next());//输出{ value: 1, done: false }
console.log(iterator.next());//输出{ value: 2, done: false }
console.log(iterator.next());//输出{ value: 3, done: false }
console.log(iterator.next());//输出{ value: undefined, done: true }
怎么算可迭代?

只要使用上面所说的迭代器对象去迭代了数据,我们就说这(个/组)数据是可迭代的,哪怕是简单的1 2 3。而在js中会把迭代器函数添加到对象的[Symbol.iterator]方法上(如上面的遍历对象),这样就可以使用js提供的(for...of)等一系列可迭代对象能够使用的方法,同样这也是一种规范,当你使用(for...of)来遍历某对象的时候,js对于是否可迭代只会看对象身上是否实现了这个玩意儿。

可迭代和可遍历有区别吗?

是否可迭代其实就是取决于你是否实现了迭代器,利用迭代器去访问数据就是可迭代。遍历是一种依次访问数据的统称,不限于for,还是递归等。个人认为迭代只是遍历的一种实现方式。

总结

js中迭代器只是具有特殊结构的对象,具有图一所示结构的可以称作是迭代器。通过迭代器我们可以统一数据的访问方式,从而调用内置的一些api,判断js对象是否可迭代主要是取决于对象身上是否实现了[Symbol.iterator]方法,迭代对象也是一种遍历,而遍历是依次访问数据这种过程的称号,不限于循环和递归

牢骚

js上手三天,学过java甚至不要三天,但是学好三个月都做不到,js总是让我又爱又恨,爱它的放荡不羁,同时也恨它的过于随意。除去这个,js还因为一些历史遗留问题,很多东西都在打补丁或者迁移,更新的比较快。 总是有一些莫名其妙的概念和全新的API,比如纯函数,高阶函数,宏任务微任务,代理反射等等。

第一次写文章,理解有误的地方欢迎大佬们指导批评,后面会分享更多的前端知识。下篇想写生成器和promise,我是小鱼干,热爱前端也爱算法。希望和大家一起进步。