在 ES6 之后,JavaScript 引入了一套统一的迭代机制,让各种数据结构都能以标准方式被遍历。这套机制由可迭代协议与迭代器协议共同定义,是理解 for...of 循环、解构赋值、展开运算符等特性的基础。
可迭代协议(Iterable Protocol)
简单来说,一个对象只要实现了 [Symbol.iterator] 方法,并且该方法返回一个迭代器,那这个对象就是可迭代对象。引擎内部通过检查该方法的存在性来判断对象是否可迭代。
const arr = [1, 2, 3];
typeof arr[Symbol.iterator]; // "function"
// arr 是可迭代对象
迭代器协议(Iterator Protocol)
迭代器是具体执行遍历操作的对象,它必须提供一个 next() 方法,每次调用返回形如 { value, done } 的结果对象:
value:本次取出的值(仅在done为false时有意义)done:布尔值,true表示遍历已结束,false表示还有更多值
const iterator = arr[Symbol.iterator]();
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 }
一旦 done 为 true,后续 next() 通常也返回 done: true,代表迭代结束。
内置可迭代对象
语言中已经为很多常用结构实现了可迭代协议,你可以直接使用 for...of 或展开它们:
- 数组:
Array - 字符串:
String - 类型化数组:
Int8Array等 Map、Set- 函数的
arguments对象 - DOM 的
NodeList(如document.querySelectorAll的返回值)
const str = "hello";
for (const ch of str) {
console.log(ch); // h, e, l, l, o
}
const set = new Set([10, 20, 30]);
console.log([...set]); // [10, 20, 30]
自定义可迭代对象
你可以让自己的对象也参与这套标准。只需要给它添加 [Symbol.iterator] 方法,返回一个遵循迭代器协议的对象即可:
const myIterable = {
data: [10, 20, 30],
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
next() {
if (index < data.length) {
return { value: data[index++], done: false };
} else {
return { done: true };
}
}
};
}
};
for (const val of myIterable) {
console.log(val); // 10, 20, 30
}
console.log([...myIterable]); // [10, 20, 30]
生成器函数也会自动返回迭代器,因此用 * 和 yield 可以更简洁地实现可迭代协议(详见后续 Generator 小节)。
实际应用场景
迭代协议不仅仅服务于 for...of,它还驱动了以下常用语法:
- 展开运算符
[...obj]:将可迭代对象的元素逐个展开 - 解构赋值
[a, b] = obj:从可迭代对象中按顺序取值 Array.from(obj):将可迭代对象(或类数组对象)转换为真正的数组- 许多内置 API 也会接受可迭代对象作为参数,如
Promise.all(iterable)、Map构造函数
为什么普通对象不是可迭代的?
普通对象({})默认没有实现迭代协议,因为对象的键是无序的,按什么顺序迭代没有统一的语义——是按添加顺序,还是按键的字母序?所以 ES6 并没有强制要求,而是留给了开发者自行决定。如果确实需要遍历对象属性,可以使用 Object.keys()、Object.values()、Object.entries(),它们返回的都是数组(可迭代对象)。
总结:迭代协议的实用价值
迭代协议的本质是将“如何遍历数据”从“数据本身”中解耦。你不需要知道一个集合内部是数组、链表还是树结构,只要它实现了迭代协议,就能用统一的语法消费所有值。这让代码更具表达力、更通用,也为生成器、异步迭代器等高级特性铺平了道路。理解并善用迭代协议,会让你的 JavaScript 代码更加简洁和健壮。