一文彻底搞清 Iterator(遍历器)概念及用法
目录
一、由来及意义
二、具体实现流程
三、具有默认 Iterator 接口的数据结构
四、调用 Iterator 接口的场合
五、总结

一、由来及意义
Javascript中表示“集合”的数据结构,主要是 Array、Object、Map、Set 这四种数据集合,除此之外,它们相互之间还可以组合使用,例如Array的成员是Map,Map的成员是Object等。因此Javascript得需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,可以为各种不同的数据结构提供一种访问机制(访问接口),任何数据结构部署Iterator接口,就可以完成该数据结构成员的遍历操作(Iterator 接口主要供for...of使用)。
二、具体实现流程
Iterator的遍历过程:
1. 创建一个指针对象,指向数据解构的起始位置。
2. 第一次调用指针对象的next()方法,指针指向数据结构的第一个成员。
3. 第二次调用指针对象的next()方法,指针指向数据结构的第二个成员。
4. 不停调用指针对象的next()方法,直到它指向数据结构结束的位置。(类似于C语言中的链表)
每一次调用next方法,都会返回数据结构中被指针指向的成员的信息。该信息为一个对象,其中包含value和done两个属性的对象{ value: something , done: false },value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束(done:false:表示循环还没有结束,done:true :表示循环结束了)。
模拟next方法返回值例子:
下面代码定义了一个makeIterator函数,它是一个遍历器生成函数,作用就是返回一个遍历器对象。对数组['前端','收割','机']执行这个函数,就会返回该数组的遍历器对象(即指针对象)goodjob。
function makeIterator(array){var index = 0;//形成闭包保存指针指向位置index,通过三元表达式返回当前信息对象。return {next: function(){return index < array.length ? {value: array[index++], done: false} : {value: undefined, done: true};}}
}var goodjob = makeIterator(['前端','收割','机'])goodjob.next()// {value: '前端', done: false}
goodjob.next()// {value: '收割', done: false}
goodjob.next()// {value: '机', done: false}
goodjob.next()// {value: undefined, done: false}
由于 Iterator 只是把接口规格加到数据结构之上,所以,遍历器与它所遍历的那个数据结构,实际上是分开的,完全可以写出没有对应数据结构的遍历器对象,或者说用遍历器对象模拟出数据结构。
另一种写法:
function makeIterator(array) {var index = 0; //形成闭包保存指针指向位置index,通过if分支语句返回当前信息对象。return {next: function() {var result = {value: undefined, done: true};if (index < array.length) {result = { value: array[index++], done: false };}return result;}};
}// 使用示例
var iterable = makeIterator([1, 2, 3]);
console.log(iterable.next().value); // 输出: 1
console.log(iterable.next().value); // 输出: 2
console.log(iterable.next().value); // 输出: 3
console.log(iterable.next().value); // undefined,迭代结束
makeIterator 这个函数的目的是创建一个迭代器对象,该对象可以遍历传入的数组。在JavaScript中,一个迭代器对象需要实现next方法,该方法在每次调用时返回数组的下一个元素。
在这个解决方案中,makeIterator函数通过返回一个对象,该对象具有一个next方法,来创建一个迭代器。每次调用next方法时,它都会返回数组中的下一个元素,直至遍历完成,此时done属性为true,value属性为undefined。这是符合ES6中Iterable和Iterator协议的定义。
三、具有默认 Iterator 接口的数据结构
Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。
因此,当某种数据结构具有Iterator 接口,即表示该数据结构是可遍历的(iterable)。
默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内。
const object1 = {[Symbol.iterator] : function () {return {next: function () {return {value: 1,done: true};}};}
};
对象object1是可遍历的(iterable),因为具有Symbol.iterator属性。执行这个属性,会返回一个遍历器对象。该对象的根本特征就是具有next方法。每次调用next方法,都会返回一个代表当前成员的信息对象,具有value和done两个属性。
凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。即不用任何处理,就可以被for...of循环遍历。
原生具备 Iterator 接口的数据结构:
- Array
- Map
- Set
- String
- TypedArray
- NodeList 对象
- 函数的 arguments 对象
数组的Symbol.iterator属性:
let arr = ['前端', '收割', '机'];
let iterator = arr[Symbol.iterator](); //因为arr中属性Symbol.iterator返回的是一个函数,//所以在[Symbol.iterator]后面添加(),使函数执行,返回一个遍历器对象iterator.next() // { value: '前端', done: false }
iterator.next() // { value: '收割', done: false }
iterator.next() // { value: '机', done: false }
iterator.next() // { value: undefined, done: true }
数组通过for of调用iterator接口生成的遍历器
const arr = ['前端', '收割', '机'];for(let v of arr) {console.log(v); // 前端 收割 机
}
Map通过for of调用iterator接口生成的遍历器
var handsome = new Map();
handsome.set("GuangHui", "1handsome");
handsome.set("JiaHao", "2handsome");
handsome.set("NingDong", 666);
for (var [name, value] of handsome) {console.log(name + ": " + value);
}
// GuangHui: 1handsome
// JiaHao: 2handsome
// NingDong: 666
Set 通过for of调用iterator接口生成的遍历器
var handsome = new Set(["GuangHui", "JiaHao", "NingDong"]);
for (var boy of handsome) {console.log(boy);
}
// GuangHui
// JiaHao
// NingDong
类数组对象通过for of调用iterator接口生成的遍历器
// 字符串
let str = "前端收割机";for (let s of str) {console.log(s); // 前 端 收 割 机
}// DOM NodeList对象
let paras = document.querySelectorAll("p");for (let p of paras) {p.classList.add("前端收割机");
}// arguments对象
function printArgs() {for (let x of arguments) {console.log(x);}
}
printArgs('前端', '收割机');
// '前端'
// '收割机'
对于不具备Iterator接口的数据结构(主要是对象)都需要自己在Symbol.iterator属性上面部署,这样才会被for...of循环遍历。
原因:对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。
下面是为对象添加 Iterator 接口的例子:
let object2 = {data: [ '前端', '收割','机'],[Symbol.iterator]() {const self = this; //将this指向赋予selflet index = 0; //初始遍历下标return {next() {if (index < self.data.length) {return {value: self.data[index++], //每次调用,遍历下标自增1done: false};} else {return { value: undefined, done: true }; //遍历结束返回该对象}}};}
};
对于类似数组的对象(存在数值键名和length属性),部署 Iterator 接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的 Iterator 接口。
let object3 = {0: '前端',1: '收割',2: '机',length: 3,[Symbol.iterator]: Array.prototype[Symbol.iterator] //直接引用数组构造函数prototype中的 Symbol.iterator属性
};
for (let item of object3) {console.log(item); // '前端', '收割', '机'
}
注意,普通对象部署特定数据结构的Symbol.iterator方法,并无效果。例如普通对象部署数组的Symbol.iterator方法。
let object4 = { //该对象不存在数值键名a: '前端',b: '收割',c: '机',length: 3,[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of object4) {console.log(item); // undefined, undefined, undefined
}
如果Symbol.iterator方法对应的不是遍历器生成函数(即会返回一个遍历器对象),解释引擎将会报错。
var object5 = {};obj[Symbol.iterator] = () => '前端收割机'; //返回的是一个字符串[...object5] // TypeError: [] is not a function
四、调用 Iterator 接口的场合
除了for...of循环,某些场景会默认调用 Iterator 接口(即Symbol.iterator方法)
番外:一文带你了解JavaScript中的for...of循环
注意事项
尽管 for...of 循环非常方便和实用,但在使用时仍需要注意一些细节和注意事项,以避免潜在的 bug 或错误。首先,需要注意的是,for...of 循环只能用于遍历实现了迭代器协议的对象。如果我们尝试使用 for...of 循环遍历一个不支持迭代器协议的对象,会导致 TypeError 错误。
其次,需要注意的是,for...of 循环只能访问迭代器的值,而不能访问迭代器的索引或键。如果我们需要访问迭代器的索引或键,可以考虑使用其他循环结构,如 for 循环或 forEach 方法。
最后,需要注意的是,for...of 循环中的迭代变量是一个常量,其值在每次迭代中都会被重新赋值。因此,我们不能在循环中修改迭代变量的值,否则会导致错误。
解构赋值
let set = new Set().add('前端').add('收割').add('机'); //Set通过add方法进行链式添加值let [one,two] = set;
// x='前端'; y='收割'let [one, ...two] = set;
// one='前端'; two=['收割','机'];
扩展运算符
// 例一
var str = '前端收割机';
[...str] // ['前','端','收','割','机']// 例二
let arr = ['是', '靓'];
['我', ...arr, '仔']
// ['我', '是', '靓', '仔']
扩展运算符内部就调用 Iterator 接口,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,通过扩展运算符,转为数组。
yield*
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
let generator = function* () {yield "我";yield* ["是","靓","仔"];yield "啊";
};var iterator = generator();iterator.next() // { value: "我", done: false }
iterator.next() // { value: "是", done: false }
iterator.next() // { value: "靓", done: false }
iterator.next() // { value: "仔", done: false }
iterator.next() // { value: "啊", done: false }
iterator.next() // { value: undefined, done: true }
Array.from()
往Array.from()函数以类数组形式输入值,Array.from()函数调用Iterator接口,将输入的类数组转化成数组
let arrayLike = {0: '前端', 1: '收割',2: '机',3: ['GuangHui','JiaHao','NingDong'],'length': 4
}
let arr = Array.from(arrayLike)
console.log(arr) // ['前端','收割','机',['GuangHui','JiaHao','NingDong']]
Map(), Set(), WeakMap(), WeakSet()
往Map构造函数以数组形式输入键值对,新建Map对象时,Map构造函数调用Iterator接口,遍历存入键值对
var goodJob = new Map([['前端',1],['收割机',2]])
Promise.all()
往Promise.all()函数以Promise数组形式输入值,Promise.all()函数调用Iterator接口,将promise请求遍历执行。
const p = Promise.all([p1, p2, p3]);
Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
Promise.race()
往Promise.race()函数以Promise数组形式输入值,Promise.race()函数调用Iterator接口,将promise请求遍历执行。
const p = Promise.race([p1, p2, p3]);
Promise.race()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.race()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
● 补充:Generator 函数与 Iterator 接口的关系:
对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
五、总结
▲遍历器(Iterator)可以为各种不同的数据结构提供一种访问机制(访问接口),任何数据结构部署Iterator接口,就可以完成该数据解构成员的遍历操作(Iterator 接口主要供for...of使用)。
▲Iterator的遍历过程:创建一个指针对象,指向数据解构的起始位置。不停调用指针对象的next()方法,指针往后移动,直到它指向数据结构结束的位置。每一次调用next方法,都会返回数据结构中被指针指向的成员的信息{ value: something , done: false }。
▲Array,Map,Set,String,TypedArray,NodeList 对象,函数的 arguments 对象为原生具备 Iterator 接口的数据结构。
▲调用 Iterator 接口的场合:for...of循环、解构赋值、扩展运算符、yield*、Array.from()、Map()、Set()、 WeakMap()、 WeakSet()、Promise.all()、Promise.race()。
✔ 参考资料
JavaScript中Iterator迭代器接口和循环_脚本之家 | Iterator(遍历器)接口概念以及用法
JavaScript中Iterator迭代器接口和循环 - Python技术站 | JS– Iterator接口 | JS_ES6 - Iterator

相关文章:
一文彻底搞清 Iterator(遍历器)概念及用法
目录 一、由来及意义 二、具体实现流程 三、具有默认 Iterator 接口的数据结构 四、调用 Iterator 接口的场合 五、总结 一、由来及意义 Javascript中表示“集合”的数据结构,主要是 Array、Object、Map、Set 这四种数据集合,除此之外,…...
稀疏矩阵的三元组表表示法及其转置
1. 什么是稀疏矩阵 稀疏矩阵是指矩阵中大多数元素为零的矩阵。 从直观上讲,当元素个数低于总元素的30%时,这样的矩阵被称为稀疏矩阵。 由于该种矩阵的特点,我们在存储这种矩阵时,如果直接采用二维数组,就会十分浪费…...
docker安装rabbitMQ,并且创建账号
# 创建docker容器启动,挂到后台运行 docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.13-management # 打开防火墙 sudo firewall-cmd --zonepublic --add-port5672/tcp --permanent sudo firewall-cmd --zonepublic --add-port15672/tcp --permanent s…...
wireshark解析grpc/protobuf的方法
1,wireshark需要安装3.20以上 下载地址:https://www.wireshark.org/ 2,如果版本不对,需要卸载,卸载方法: sudo rm -rf /Applications/Wireshark.app sudo rm -rf $HOME/.config/wireshark sudo rm -rf /…...
软件测试用例(2)
具体的设计方法 -- 黑盒测试 因果图 因果图是一种简化的逻辑图, 能直观地表明程序的输入条件(原因)和输出动作(结果)之间的相互关系. 因果图法是借助图形来设计测试用例的一种系统方法, 特别适用于被测试程序具有多种输入条件, 程序的输出又依赖于输入条件的各种情况. 因果图…...
集群式无人机仿真环境和数据集
仿真环境和数据集 Quick StartAcknowledgementsSwarmSim Quick Start Compiling tests passed on 20.04 with ros installed. You can just execute the following commands one by one. # Download the Simulator and run it wget https://cloud.tsinghua.edu.cn/library/34…...
IPSec VPN
IP Security,IP安全 1、特点 L3的VPN 缺:不支持组播、配置复杂、延迟增加、资源消耗较多 优:具备访问控制、密码学四个维度、抗重放打击 2、组件 ①安全协议 1)验证头技术(AH) IP协议号51 提供数据完整性检查,身份验证,抗重放攻击 无法做数据的机密性 AH的完…...
docker部署nacos,单例模式(standalone),使用内置的derby数据库,简易安装
文章目录 前言安装创建文件夹docker指令安装docker指令安装-瘦身版 制作docker-compose.yaml文件查看页面 前言 nacos作为主流的服务发现中心和配置中心,广泛应用于springcloud框架中,现在就让我们一起简易的部署一个单例模式的nacos,版本可…...
systemd监听服务配置文件更新自动重启服务
背景&需求 需要频繁更改一个服务的配置文件进行测试 实现 配置服务的systemd文件 vim /lib/systemd/system/xxx.service [Unit] Descriptionxxx daemon, A rule-based proxy in Go.[Service] Typesimple ExecStart/opt/xxx/xxx-d /etc/xxx/ Restartalways[Install] Wan…...
【yy讲解PostCSS是如何安装和使用】
🎥博主:程序员不想YY啊 💫CSDN优质创作者,CSDN实力新星,CSDN博客专家 🤗点赞🎈收藏⭐再看💫养成习惯 ✨希望本文对您有所裨益,如有不足之处,欢迎在评论区提出…...
YOLO电动车检测识别数据集:12617张图像,yolo标注完整
YOLO电动车检测识别数据集:12617张图像,电动车一类,yolo标注完整,部分图像应用增强。 适用于CV项目,毕设,科研,实验等 需要此数据集或其他任何数据集请私信...
从汇编看函数调用
文章目录 函数调用流程栈相关寄存器及的作用简介寄存器功能指令功能 栈函数的括号{}正括号反括号 参数传递传值,变量不可改传指针,变量可改C 传引用 函数调用实例 函数调用流程 目标:函数调用前后栈保持不变 保存main函数的寄存器上下文移…...
node.js的错误处理
当我打开一个不存在的文件时,错误如下: 在读取文件里面写入console.log(err),在控制台中可以看到我的错误代码类型:文件不存在的错误代码 ENOENT。见更多错误代码---打开node.js官方API文档Error 错误 | N…...
shell的编写
文章目录 1.框架2.命令行3.获取用户命令字符串4.命令行字符串分割5.执行命令和内建命令6.完整代码: 1.框架 我们知道shell是一直存在的,所以首先我们第一步就是要搭建一个框架,使其一直存在。 那么也很简单,一个while循环就可以完…...
css心跳动画
图标引入 <img class"icon" src"heart.svg" alt"" srcset""> CSS代码 <style>.icon {animation:bpm 1s linear,pulse 0.75s 1s linear infinite;}keyframes pulse {from,75%,to {transform: scale(1);}25% {transform:…...
在 Amazon Timestream 上通过时序数据机器学习进行预测分析
由于不断变化的需求和现代化基础设施的动态性质,为大型应用程序规划容量可能会非常困难。例如,传统的反应式方法依赖于某些 DevOps 指标(如 CPU 和内存)的静态阈值,而这些指标在这样的环境中并不足以解决问题。在这篇文…...
【智能排班系统】快速消费线程池
文章目录 线程池介绍线程池核心参数核心线程数(Core Pool Size)最大线程数(Maximum Pool Size)队列(Queue)线程空闲超时时间(KeepAliveTime)拒绝策略(RejectedExecutionH…...
C语言——内存函数
前言: C语言中除了字符串函数和字符函数外,还有一些函数可以直接对内存进行操作,这些函数被称为内存函数,这些函数与字符串函数都属于<string.h>这个头文件中。 一.memcpy()函数 memcpy是C语言中的…...
ideaSSM图书借阅管理系统VS开发mysql数据库web结构java编程计算机网页源码maven项目
一、源码特点 SSM 图书借阅管理系统是一套完善的信息管理系统,结合SSM框架和bootstrap完成本系统,对理解JSP java编程开发语言有帮助系统采用SSM框架(MVC模式开发),系统具有完整的源代码 和数据库,系统主…...
普联一面4.2面试记录
普联一面4.2面试记录 文章目录 普联一面4.2面试记录1.jdk和jre的区别2.java的容器有哪些3.list set map的区别4.get和post的区别5.哪个更安全6.java哪些集合类是线程安全的7.创建线程有哪几种方式8.线程的状态有哪几种9.线程的run和start的区别10.什么是java序列化11.redis的优…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
