深入理解 JavaScript 迭代器:从原理到实战
在现代前端开发中,我们经常使用 for...of
、展开运算符 ...
、Array.from()
等语法,但你是否想过:为什么不同的数据结构都能用同一种方式遍历? 这背后的核心机制,就是 迭代器(Iterator)。
本文将带你从零开始,全面掌握 JavaScript 中的迭代器,包括其设计思想、实现原理、常见应用和面试高频考点。
一、什么是迭代器?为什么需要它?
1.1 问题背景
JavaScript 有多种数据结构:
js
const arr = [1, 2, 3];
const str = "hello";
const set = new Set([1, 2, 3]);
const map = new Map([['a', 1], ['b', 2]]);
它们的内部存储方式完全不同,但我们都希望用统一的方式遍历它们:
js
for (const item of arr) // 数组
for (const char of str) // 字符串
for (const item of set) // Set
for (const [k, v] of map) // Map
👉 如何实现这种"统一遍历"?答案就是:迭代器协议。
二、迭代器的核心:可迭代协议(Iterable Protocol)
2.1 两个关键概念
概念 | 说明 |
---|---|
可迭代对象(Iterable) | 实现了 [Symbol.iterator]() 方法的对象 |
迭代器(Iterator) | 有 .next() 方法的对象,返回 { value, done } |
2.2 for...of
的工作原理
当你写:
js
for (const item of arr) {
console.log(item);
}
JavaScript 实际执行:
js
const iterator = arr[Symbol.iterator](); // 获取迭代器
let result = iterator.next();
while (!result.done) {
console.log(result.value);
result = iterator.next();
}
2.3 手写一个迭代器
js
function makeIterator(array) {
let nextIndex = 0;
return {
next() {
return nextIndex < array.length
? { value: array[nextIndex++], done: false }
: { done: true };
}
};
}
const iter = makeIterator([1, 2, 3]);
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { done: true }
三、让任意对象支持 for...of
3.1 为什么普通对象不能用 for...of
?
js
const obj = { a: 1, b: 2 };
for (const item of obj) {
// ❌ TypeError: obj is not iterable
}
因为普通对象没有实现 Symbol.iterator
方法。
3.2 让对象"可迭代"
js
const obj = {
a: 1,
b: 2,
[Symbol.iterator]() {
const values = Object.values(this);
let index = 0;
return {
next() {
if (index < values.length) {
return { value: values[index++], done: false };
} else {
return { done: true };
}
}
};
}
};
for (const val of obj) {
console.log(val); // 1, 2
}
四、生成器:迭代器的语法糖
手动写迭代器太繁琐?生成器函数(function*
) 可以自动帮你生成迭代器。
4.1 基本用法
js
function* gen() {
yield 1;
yield 2;
yield 3;
}
const iter = gen();
iter.next(); // { value: 1, done: false }
iter.next(); // { value: 2, done: false }
4.2 实现 range(1, 5)
js
function* range(from, to) {
for (let i = from; i <= to; i++) {
yield i;
}
}
for (const n of range(1, 3)) {
console.log(n); // 1, 2, 3
}
五、yield*
:委托给另一个可迭代对象
yield*
可以将遍历任务"委托"给另一个可迭代对象。
js
function* gen() {
yield* [1, 2];
yield* 'ab';
yield* new Set([3, 4]);
}
for (const x of gen()) {
console.log(x); // 1, 2, 'a', 'b', 3, 4
}
六、哪些语法依赖迭代器?
任何接受"可迭代对象"的语法,都会调用其 Symbol.iterator
方法:
语法 | 示例 |
---|---|
for...of |
for (const x of arr) |
展开运算符 ... |
const newArr = [...arr] |
数组解构 | const [a, b] = arr |
Array.from() |
Array.from(iterable) |
new Map() / new Set() |
new Set([1,2,3]) |
Promise.all() |
Promise.all([p1, p2]) |
七、经典面试题:让对象支持数组解构
题目
js
const [a, b] = { c: 1, d: 2 };
console.log(a, b); // 如何输出 1, 2?
解法
js
Object.prototype[Symbol.iterator] = function* () {
for (const key of Object.keys(this)) {
yield this[key];
}
};
const [a, b] = { c: 1, d: 2 };
console.log(a, b); // 1, 2 ✅
八、实际应用场景
8.1 惰性求值(Lazy Evaluation)
js
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
// 可以无限生成,但只在需要时计算
8.2 合并有序数组(归并排序思想)
js
function* mergeSorted(arr1, arr2) {
let i = 0, j = 0;
while (i < arr1.length && j < arr2.length) {
yield arr1[i] < arr2[j] ? arr1[i++] : arr2[j++];
}
while (i < arr1.length) yield arr1[i++];
while (j < arr2.length) yield arr2[j++];
}
console.log([...mergeSorted([1,3,5], [2,4,6])]); // [1,2,3,4,5,6]