JavaScript Iterator协议与for-of的自定义可迭代对象
JavaScript 原生的 Array 和 String 类型可以直接使用 for...of 循环进行遍历。要让自定义的对象也能享受这种语法便利,必须让该对象符合“可迭代协议”。这意味着对象必须包含一个特定的方法,并返回正确的迭代逻辑。
核心概念解析
实现自定义可迭代对象的核心在于理解两个角色之间的关系:
- 可迭代对象:对象本身,必须拥有一个
[Symbol.iterator]属性。 - 迭代器对象:由
[Symbol.iterator]返回的对象,必须拥有一个next()方法。
next() 方法每次被调用时,都会返回一个包含两个字面的对象:
value:当前遍历到的值。done:布尔值,表示遍历是否结束(true结束,false继续)。
步骤 1:构建基础对象结构
首先定义一个简单的类构造函数,用于设定遍历的起始和结束范围。
打开你的代码编辑器或浏览器控制台,输入以下代码:
class NumberRange {
constructor(start, end) {
this.start = start;
this.end = end;
}
}
此时,这个对象仅仅是数据的容器,还不能被 for...of 遍历。如果尝试遍历,JavaScript 会抛出 TypeError: x is not iterable 错误。
步骤 2:实现 [Symbol.iterator] 方法
要让对象变得“可迭代”,需要添加一个特殊的计算属性名 [Symbol.iterator]。这个属性不需要是一个数据属性,而必须是一个函数。
修改上述代码,在类中添加如下方法:
class NumberRange {
constructor(start, end) {
this.start = start;
this.end = end;
}
[Symbol.iterator]() {
// 迭代逻辑将在这里实现
}
}
步骤 3:编写迭代器逻辑
在 [Symbol.iterator] 方法内部,必须返回一个包含 next() 方法的对象。这个对象通常可以使用闭包来维护当前的遍历状态(即当前数字是多少)。
完善 [Symbol.iterator] 方法的具体实现:
[Symbol.iterator]() {
let current = this.start; // 初始化当前指针
const last = this.end; // 记录结束边界
// 返回迭代器对象
return {
next() {
// 1. 判断是否超出边界
if (current <= last) {
// 2. 如果未超出,返回当前值,并移动指针
const value = current;
current++; // 指针后移
return { value: value, done: false };
} else {
// 3. 如果超出,标记为结束
return { done: true };
}
}
};
}
这段代码利用闭包保存了 current 变量。每次 next() 被调用时,它都会检查条件并更新状态。
步骤 4:使用 for-of 循环遍历
现在对象已经完全符合协议。实例化一个 NumberRange 对象,并使用 for...of 循环来验证效果。
输入并执行以下代码:
const myRange = new NumberRange(1, 5);
for (const num of myRange) {
console.log(num);
}
控制台将依次输出数字 1 到 5,然后自动停止。
深度解析:for-of 的执行流程
理解 for...of 背后发生了什么,有助于掌握更复杂的场景。当循环开始时,JavaScript 引擎会执行一系列固定的操作。
从流程中可以看出,只要 next() 返回的 done 为 false,循环体就会继续执行。一旦 done 变为 true,循环立即终止,且此时返回的对象中的 value 属性通常会被忽略。
进阶应用:实现无限迭代器
迭代协议并不强制要求迭代必须结束。只要控制好 done 的返回时机,就可以创建一个无限序列,例如生成斐波那契数列或无限递增的 ID。
创建一个名为 InfiniteCounter 的类:
class InfiniteCounter {
constructor(start = 0) {
this.start = start;
}
[Symbol.iterator]() {
let current = this.start;
return {
next() {
// 永远返回 done: false,实现无限循环
const value = current;
current++;
return { value: value, done: false };
}
};
}
}
注意:由于这个迭代器是无限的,切勿直接使用 for...of 而不带 break 语句,否则会导致浏览器卡死。正确的用法是手动控制迭代次数或使用解构赋值:
const counter = new InfiniteCounter(10);
const iterator = counter[Symbol.iterator]();
// 手动获取前 3 个值
console.log(iterator.next().value); // 10
console.log(iterator.next().value); // 11
console.log(iterator.next().value); // 12
或者使用带 break 的循环:
const counter = new InfiniteCounter(100);
let count = 0;
for (const num of counter) {
console.log(num);
count++;
if (count >= 3) break; // 强制退出
}
常见错误与修复
在实现自定义迭代器时,有几个高频错误点需要注意。
| 错误现象 | 可能原因 | 修正方法 |
|---|---|---|
TypeError: obj is not iterable |
对象上没有定义 [Symbol.iterator] 方法。 |
检查对象属性,确保方法名包含方括号。 |
TypeError: iterator.next is not a function |
[Symbol.iterator] 没有返回包含 next 方法的对象。 |
确保 return 语句返回的是字面量对象 { next: function(){} }。 |
| 循环只执行一次就停止 | next() 方法内部没有更新状态变量(如 current 没有自增)。 |
确认 next 内部修改了闭包变量或对象属性。 |
输出 undefined |
done 为 false 时,返回对象中缺少 value 字段。 |
保证返回对象结构完整:{ value: ..., done: ... }。 |

暂无评论,快来抢沙发吧!