一文彻底搞清 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
,NodeLis
t 对象,函数的 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的优…...

SQLite的架构(十一)
返回:SQLite—系列文章目录 上一篇:SQLite下一代查询规划器(十) 下一篇:SQLite—系列文章目录 介绍 本文档介绍SQLite库的架构。 这里的信息对那些想要了解或 修改SQLite的内部工作原理。 接口SQL 命令处理器虚拟机B-树…...

Vue2电商前台项目(一):项目前的初始化及搭建
一、项目初始化 创建项目:sudo vue create app 1.项目配置 (1)浏览器自动打开 在package.json文件中,serve后面加上 --open "scripts": {"serve": "vue-cli-service serve --open","buil…...

4.6 offset指令,jmp short指令,far,dword ptr各种跳转指令
4.6 offset指令,jmp short指令,far,dword ptr各种跳转指令 可以修改IP,或同时修改CS和IP的指令统称为转移指令。概括的讲,转移指令就是可以控制CPU执行内存中某处代码的指令 1. 转移指令 1.1 8086CPU的转移行为有以…...

【WEEK5】 【DAY5】DML语言【中文版】
2024.3.29 Friday 目录 3.DML语言3.1.外键(了解)3.1.1.概念3.1.2.作用3.1.3.添加(书写)外键的几种方法3.1.3.1.创建表时直接在主动引用的表里写(被引用的表的被引用的部分)3.1.3.2.先创建表后修改表以添加…...

媒体偏见从何而来?--- 美国MRC(媒体评级委员会)为何而生?
每天当我们打开淘宝,京东,步入超市,逛街或者逛展会,各种广告铺天盖地而来。从原来的平面广告,到多媒体广告,到今天融合AR和VR技术的数字广告,还有元宇宙虚拟世界,还有大模型加持的智…...

Solana 线下活动回顾|多方创新实践,引领 Solana“文艺复兴”新浪潮
Solana 作为在过去一年里实现突破式飞跃的头部公链,究竟是如何与 Web3 行业共振,带来全新的技术发展与生态亮点的呢?在 3 月 24 日刚结束的「TinTin Destination Moon」活动现场,来自 Solana 生态的的专家大咖和 Web3 行业的资深人…...

CSS3 实现文本与图片横向无限滚动动画
文章目录 1. 实现效果2.html结构3. css代码 1. 实现效果 gif录屏比较卡,实际很湿滑,因为是css动画实现的 2.html结构 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"…...

Android 性能优化之黑科技开道(一)
1. 缘起 在开发电视版智家 App9.0 项目的时候,发现了一个性能问题。电视系统原本剩余的可用资源就少,而随着 9.0 功能的进一步增多,特别是门铃、门锁、多路视频同屏监控后等功能的增加,开始出现了卡顿情况。 经过调研分析发现有…...

Successive Convex Approximation算法的学习笔记
文章目录 一、代码debug二、原理 本文主要参考了CSDN上的 另一篇文章,但规范了公式的推导过程和修缮了部分代码 一、代码debug 首先,我们将所有的代码放到MATLAB中,很快在命令行中出现了错误信息 很显然有问题,但是我不知道发生…...

IoT数采平台2:文档
IoT数采平台1:开篇IoT数采平台2:文档IoT数采平台3:功能IoT数采平台4:测试 【平台功能】 基础配置、 实时监控、 规则引擎、 告警列表、 系统配置 消息通知:Websocket 设备上线、设备下线、 数据变化、 告警信息、 实时…...