JavaScript中实现迭代器需遵循可迭代协议和迭代器协议,通过定义[Symbol.iterator]方法返回具备next()方法的迭代器对象,从而支持for...of和展开运算符;该机制统一了数据结构的遍历接口,实现惰性求值,适用于自定义对象、树、图及无限序列等复杂场景,提升代码通用性与性能。

JavaScript中实现迭代器,核心在于遵循“迭代器协议”和“可迭代协议”。说白了,就是让你的数据结构能够被
for...of
...
[Symbol.iterator]
next()
value
done
要让一个自定义对象或数据结构可迭代,你需要:
[Symbol.iterator]
[Symbol.iterator]
next()
next()
next()
{ value: T, done: boolean }value
done
done
true
value
undefined
举个例子,假设我们要创建一个自定义的
Range
class MyRange {
constructor(start, end) {
this.start = start;
this.end = end;
}
[Symbol.iterator]() {
let current = this.start;
const end = this.end; // 缓存end,避免在闭包中引用this
return {
next() {
if (current <= end) {
return { value: current++, done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
}
// 使用
const myNumbers = new MyRange(1, 5);
for (const num of myNumbers) {
console.log(num); // 1, 2, 3, 4, 5
}
// 或者用展开运算符
console.log([...myNumbers]); // [1, 2, 3, 4, 5]这里,
MyRange
[Symbol.iterator]
next()
current
在我看来,迭代器这东西,它真正解决的是一个“统一接口”的问题。想想看,以前我们要遍历数组,用
for
for...in
forEach
keys()
values()
entries()
迭代器协议的出现,就像是给所有可遍历的数据结构定了一个“君子协定”:只要你实现了
[Symbol.iterator]
next()
for...of
更深层次一点,它还引入了“惰性求值”的概念。我的
MyRange
end
end
提到迭代器,就不得不提生成器(Generators)。很多时候,大家会把它们混为一谈,但其实它们是两种不同的概念,只不过生成器是实现迭代器的一种“语法糖”,或者说,一种更优雅、更方便的工具。
迭代器是协议,是行为规范,是“你得有个
next()
{value, done}当你写一个
function*
[Symbol.iterator]
next()
yield
yield
next()
next()
function* simpleGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = simpleGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
// 也可以直接用for...of
for (const val of simpleGenerator()) {
console.log(val); // 1, 2, 3
}你看,用生成器写迭代器,是不是比手动维护
current
next()
那么什么时候会直接手写迭代器呢?嗯,可能是在一些非常底层、需要极致性能优化,或者你正在构建一个非常复杂的、需要精细控制迭代过程的库时。比如,你可能需要一个迭代器,它不仅仅是顺序遍历,还可能根据某些条件跳过元素,或者在遍历过程中修改自身状态。生成器虽然强大,但它的
yield
在实际项目中,尤其是在处理一些非线性的复杂数据结构,比如树、图的时候,迭代器的价值就体现得淋漓尽致了。你不能简单地用一个
for
实现特定遍历策略的迭代器: 例如,对于一棵二叉树,你可能需要前序遍历、中序遍历或后序遍历。你可以为每种遍历方式实现一个独立的迭代器。
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
// 假设我们想实现一个中序遍历的迭代器
class InOrderTreeIterator {
constructor(root) {
this.stack = [];
this._pushLeft(root);
}
_pushLeft(node) {
while (node) {
this.stack.push(node);
node = node.left;
}
}
next() {
if (this.stack.length === 0) {
return { value: undefined, done: true };
}
const node = this.stack.pop();
this._pushLeft(node.right); // 处理右子树
return { value: node.value, done: false };
}
}
class BinaryTree {
constructor(root) {
this.root = root;
}
[Symbol.iterator]() {
// 默认返回中序遍历迭代器
return new InOrderTreeIterator(this.root);
}
}
// 示例使用
const root = new TreeNode(4);
root.left = new TreeNode(2);
root.right = new TreeNode(5);
root.left.left = new TreeNode(1);
root.left.right = new TreeNode(3);
const tree = new BinaryTree(root);
console.log([...tree]); // [1, 2, 3, 4, 5]这里,
InOrderTreeIterator
链式迭代器或组合迭代器: 设想你有多个数据源,你希望像一个整体一样去遍历它们。你可以创建迭代器,将它们串联起来。比如,一个
ConcatIterator
FilterIterator
function* filterIterable(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) {
yield item;
}
}
}
const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = filterIterable(numbers, n => n % 2 === 0);
console.log([...evenNumbers]); // [2, 4, 6]这里,我们用一个生成器函数实现了过滤器,它接受一个可迭代对象和一个谓词函数,然后惰性地产生符合条件的元素。这其实就是函数式编程中常见的
filter
无限序列的迭代器: 迭代器非常适合表示无限序列,因为它们是惰性求值的。比如,一个生成斐波那契数列的迭代器:
function* fibonacciSequence() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacciSequence();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
// ...你可以一直调用next()你不能把一个无限序列放到一个数组里,那会耗尽内存。但有了迭代器,你可以按需获取序列中的任何一个元素。
总的来说,迭代器模式在JavaScript中提供了一种非常强大和灵活的遍历机制。它不仅仅是让
for...of
以上就是JS如何实现迭代器?迭代器协议的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号