for...of 的秘密:迭代器与可迭代对象,你也能创造“可循环”的东西

为什么数组可以用for...of循环?为什么对象不行?今天我们来揭开JS里"可循环"的秘密------迭代器(Iterator)和可迭代对象(Iterable)。弄懂它们,你就能让自己的对象也支持for...of,甚至还能写出像Python生成器那样优雅的代码。

前言

你有没有好奇过,为什么数组可以用for...of遍历,而对象不行?为什么...扩展运算符可以展开数组,却不能直接展开对象?这背后其实是迭代器协议在起作用。

今天我们就来彻底搞懂这套机制,然后亲手造一个可以for...of遍历的对象。看完你会感叹:原来JS的循环还有这么多骚操作!

一、什么是可迭代对象?

如果一个对象实现了可迭代协议 ,它就是可迭代对象。可迭代协议要求对象有一个[Symbol.iterator]方法,这个方法返回一个迭代器

简单来说:可迭代对象 = 有一个能返回迭代器的方法

数组、字符串、Map、Set、arguments、NodeList等都是原生可迭代对象。所以你可以:

js 复制代码
for (let item of [1,2,3]) { console.log(item); } // 数组
for (let char of 'hello') { console.log(char); } // 字符串
for (let [key,val] of new Map([[1,2]])) { } // Map

对象不是可迭代对象,所以for...of直接遍历对象会报错。

二、迭代器长什么样?

迭代器是一个对象,它有一个next()方法。每次调用next(),会返回一个对象:{ value: 任意值, done: boolean }done表示是否遍历结束。

比如手动创建一个数组的迭代器:

js 复制代码
const arr = ['a', 'b', 'c'];
const iterator = arr[Symbol.iterator]();

console.log(iterator.next()); // { value: 'a', done: false }
console.log(iterator.next()); // { value: 'b', done: false }
console.log(iterator.next()); // { value: 'c', done: false }
console.log(iterator.next()); // { value: undefined, done: true }

你看,这个迭代器就像个"读取器",每次取一个值,直到取完。

三、自己实现一个可迭代对象

现在我们来造一个可以for...of遍历的对象。比如一个范围对象,能遍历从start到end的所有整数。

js 复制代码
const range = {
  start: 1,
  end: 5,
  [Symbol.iterator]() {
    let current = this.start;
    const end = this.end;
    return {
      next() {
        if (current <= end) {
          return { value: current++, done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

for (let num of range) {
  console.log(num); // 1,2,3,4,5
}

就这么简单!只要对象有[Symbol.iterator]方法,并且返回一个带有next的对象,它就能被for...of遍历。

四、扩展运算符、解构赋值背后的迭代器

很多JS语法都依赖迭代器:

  • ...扩展运算符:把可迭代对象展开成元素列表
  • 数组解构:[a, b, ...rest] = iterable
  • Array.from():把可迭代对象转成数组
  • for...of循环
  • Promise.all()Promise.race()的参数也是可迭代对象

所以,只要你的对象是可迭代的,它就能享受这些语法糖。

js 复制代码
const numbers = [...range]; // [1,2,3,4,5]
const [first, second, ...rest] = range; // first=1, second=2, rest=[3,4,5]

五、生成器函数:迭代器的快捷方式

还记得昨天的Generator吗?生成器函数返回的就是迭代器!所以我们可以用Generator来简化上面的代码:

js 复制代码
const range = {
  start: 1,
  end: 5,
  *[Symbol.iterator]() {
    for (let i = this.start; i <= this.end; i++) {
      yield i;
    }
  }
};

是不是简洁多了?*[Symbol.iterator]()就是Generator方法,每次yield一个值,for...of会自动调用next

六、无限迭代器:永不停止的循环

迭代器可以无限进行下去,比如生成斐波那契数列:

js 复制代码
const fibonacci = {
  *[Symbol.iterator]() {
    let a = 0, b = 1;
    while (true) {
      yield a;
      [a, b] = [b, a + b];
    }
  }
};

const fib = fibonacci[Symbol.iterator]();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
// 想取多少取多少

但注意:用for...of遍历无限迭代器会死循环,所以要手动控制。

七、提前终止迭代器:return方法

如果迭代器被提前终止(比如for...of中遇到break,或者解构只取前几个值),JS会调用迭代器的return方法(如果有的话)。这可以用来做清理工作。

js 复制代码
const specialIterable = {
  [Symbol.iterator]() {
    let i = 0;
    return {
      next() {
        if (i < 3) return { value: i++, done: false };
        return { done: true };
      },
      return() {
        console.log('提前终止了');
        return { done: true };
      }
    };
  }
};

for (let x of specialIterable) {
  console.log(x);
  if (x === 1) break; // 触发return
}
// 输出:0,1, 然后打印"提前终止了"

八、实际应用:让对象可迭代

假设你有一个用户列表对象,你想让它支持for...of直接遍历用户:

js 复制代码
const userList = {
  users: [
    { name: '张三', age: 18 },
    { name: '李四', age: 20 },
    { name: '王五', age: 22 }
  ],
  *[Symbol.iterator]() {
    for (let user of this.users) {
      yield user;
    }
  }
};

for (let user of userList) {
  console.log(user.name); // 张三 李四 王五
}

这样,你的自定义对象就能像数组一样优雅地遍历了。

九、总结:迭代器无处不在

  • 可迭代对象 :实现了[Symbol.iterator]方法,返回一个迭代器。
  • 迭代器 :实现了next()方法,返回{ value, done }
  • 生成器函数:是迭代器最便捷的实现方式。
  • 很多JS语法(for...of、扩展运算符、解构)都依赖迭代器协议。

理解了这套机制,你就能:

  • 让自定义对象支持for...of
  • 创建无限序列
  • 深入理解JS语法糖背后的原理

下次你写for...of时,脑子里可以浮现出迭代器一步步next的画面------这才是真正掌握了JS的底层。

明天我们将进入DOM操作与事件流,从JS的核心走向与页面的交互。如果你觉得今天的文章够"可迭代",点个赞让更多人看到。我们明天见!

相关推荐
SuperEugene2 小时前
前端 Git 协作规范实战:commit message + 分支管理 + 合并流程,告别冲突与混乱|工程化与协作规范篇
前端·javascript·vue.js·git·前端框架
泯仲2 小时前
Zustand 状态管理实战详解:轻量高效的React状态方案
前端·javascript·react.js
Arthur14726122865472 小时前
useTemplateRef 详解
前端·vue.js
张一凡932 小时前
做了一个AI聊天应用后,我决定试试这个状态管理库
前端·javascript·react.js
星辰_mya2 小时前
自定义注解 + AOP:打造企业级通用组件(日志、限流、幂等)
java·开发语言·spring·面试·架构师
早點睡3902 小时前
ReactNative项目OpenHarmony三方库集成实战:react-native-confirmation-code-field
javascript·react native·react.js
竹林8182 小时前
从轮询到监听:我在NFT铸造项目中优化合约事件订阅的完整踩坑记录
前端·javascript
luckyCover2 小时前
TypeScript 学习系列(初):充分认识 TypeScript
前端
wangfpp2 小时前
产品:这个文字颜色能不能根据背景图自动换?
前端·面试·产品