0

0

JS如何实现迭代器?迭代器协议

月夜之吻

月夜之吻

发布时间:2025-08-19 14:19:01

|

638人浏览过

|

来源于php中文网

原创

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

js如何实现迭代器?迭代器协议

JavaScript中实现迭代器,核心在于遵循“迭代器协议”和“可迭代协议”。说白了,就是让你的数据结构能够被

for...of
循环或者展开运算符(
...
)这样的语法所消费。可迭代协议要求对象或其原型链上有一个名为
[Symbol.iterator]
的方法,这个方法必须返回一个迭代器。而迭代器协议则要求这个迭代器对象有一个
next()
方法,每次调用它时返回一个包含
value
(当前值)和
done
(是否遍历完成)属性的对象。

解决方案

要让一个自定义对象或数据结构可迭代,你需要:

  1. 实现可迭代协议: 在你的对象上定义一个
    [Symbol.iterator]
    方法。
  2. 实现迭代器协议:
    [Symbol.iterator]
    方法必须返回一个迭代器对象。这个迭代器对象需要有一个
    next()
    方法。
  3. 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
变量)。

为什么迭代器是现代JavaScript的基石?它解决了哪些编程痛点?

在我看来,迭代器这东西,它真正解决的是一个“统一接口”的问题。想想看,以前我们要遍历数组,用

for
循环;遍历对象属性,用
for...in
(还得小心原型链上的属性);遍历Set或Map,那又得用它们各自的
forEach
或者
keys()
values()
entries()
方法。代码写着写着,就感觉很碎片化,不同的数据结构有不同的遍历方式,这对于写通用函数或者库来说,简直是噩梦。

迭代器协议的出现,就像是给所有可遍历的数据结构定了一个“君子协定”:只要你实现了

[Symbol.iterator]
这个方法,返回一个有
next()
方法的对象,我就能用
for...of
去遍历你。这一下子,所有的数据结构,无论它是数组、字符串、Set、Map,还是你自己写的自定义数据结构,都拥有了统一的遍历接口。这极大地提升了代码的通用性和可读性。

更深层次一点,它还引入了“惰性求值”的概念。我的

MyRange
例子虽然简单,但如果我把
end
设得非常大,甚至不设
end
(比如一个无限序列),只要你不去遍历到那个点,后面的值就不会被计算出来。这对于处理大数据流、或者生成无限序列(比如斐波那契数列)时,性能优势就非常明显了。你不需要一次性把所有数据都加载到内存里,这在资源受限的环境下尤其有用。

迭代器与生成器:它们之间是怎样的关系?如何选择使用?

提到迭代器,就不得不提生成器(Generators)。很多时候,大家会把它们混为一谈,但其实它们是两种不同的概念,只不过生成器是实现迭代器的一种“语法糖”,或者说,一种更优雅、更方便的工具

迭代器是协议,是行为规范,是“你得有个

next()
方法,返回
{value, done}
”。 生成器是函数,是一种特殊的函数,它能够自动帮你实现这个迭代器协议。

当你写一个

function*
(注意函数名后面的星号)时,它就是一个生成器函数。调用这个函数,它不会立即执行里面的代码,而是返回一个生成器对象。这个生成器对象本身就符合迭代器协议和可迭代协议,也就是说,它自带了
[Symbol.iterator]
next()
方法。你可以在生成器函数内部使用
yield
关键字,每当
yield
一个值,就相当于
next()
方法返回了这个值,并且暂停了函数的执行。下次调用
next()
时,函数会从上次暂停的地方继续执行。

千鹿AI
千鹿AI

千鹿AI是一个全面的AI图像处理平台,提供AI生图、AI扩图和AI去背景等多种智能工具。

下载
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
循环去遍历它们。这时候,迭代器模式就提供了一种优雅的方式来封装遍历逻辑。

  1. 实现特定遍历策略的迭代器: 例如,对于一棵二叉树,你可能需要前序遍历、中序遍历或后序遍历。你可以为每种遍历方式实现一个独立的迭代器。

    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
    就是手动实现的一个复杂迭代器,它内部维护了一个栈来模拟递归遍历过程。这比用递归函数来获取所有节点,然后把它们放到一个数组里再遍历,要更节省内存,因为它也是惰性求值的。

  2. 链式迭代器或组合迭代器: 设想你有多个数据源,你希望像一个整体一样去遍历它们。你可以创建迭代器,将它们串联起来。比如,一个

    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
    操作的迭代器版本。

  3. 无限序列的迭代器: 迭代器非常适合表示无限序列,因为它们是惰性求值的。比如,一个生成斐波那契数列的迭代器:

    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代码更上一层楼。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
java中boolean的用法
java中boolean的用法

在Java中,boolean是一种基本数据类型,它只有两个可能的值:true和false。boolean类型经常用于条件测试,比如进行比较或者检查某个条件是否满足。想了解更多java中boolean的相关内容,可以阅读本专题下面的文章。

352

2023.11.13

java boolean类型
java boolean类型

本专题整合了java中boolean类型相关教程,阅读专题下面的文章了解更多详细内容。

34

2025.11.30

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1505

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

233

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

107

2025.10.17

php中foreach用法
php中foreach用法

本专题整合了php中foreach用法的相关介绍,阅读专题下面的文章了解更多详细教程。

117

2025.12.04

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

361

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

213

2023.09.04

AO3官网入口与中文阅读设置 AO3网页版使用与访问
AO3官网入口与中文阅读设置 AO3网页版使用与访问

本专题围绕 Archive of Our Own(AO3)官网入口展开,系统整理 AO3 最新可用官网地址、网页版访问方式、正确打开链接的方法,并详细讲解 AO3 中文界面设置、阅读语言切换及基础使用流程,帮助用户稳定访问 AO3 官网,高效完成中文阅读与作品浏览。

89

2026.02.02

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 4.5万人学习

Pandas 教程
Pandas 教程

共15课时 | 1万人学习

ASP 教程
ASP 教程

共34课时 | 4.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号