😁 作者简介:一名大三的学生,致力学习前端开发技术
⭐️个人主页:夜宵饽饽的主页
❔ 系列专栏:前端js专栏
👐学习格言:成功不是终点,失败也并非末日,最重要的是继续前进的勇气
🔥前言:
这里是关于Interator遍历器的使用详情,有关一些遍历代码,希望大家可以去打断点运行,去理解。 这是我自己的学习JavaScript的笔记,希望可以帮助到大家,欢迎大家的补充和纠正
文章目录
第15章 Iterator和for…of循环
15.1 Iterator(遍历器)的概念
遍历器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制,任何数据结构,只要部署Iterator接口,就可以完成遍历操作(即依次出来该数据结构的所有成员)
遍历出现的背景和原因:
JavaScript原有的表示集合的数据结构主要是数组和对象,ES6又添加了Map和Set。这样就有4中数据集合,用户还可以组合它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象,这样就需要一种统一的接口机制来处理所有不同的数据结构
Iterator的作用有3个
- 为各种数据结构提供一个统一的,简便的访问接口
- 使得数据结构的成员能够按某种次序排列
- ES6创造一种新的遍历命令——for…of循环
Iterator的遍历过程如下
- 创建一个指针对象,指向当前数据结构的起始位置,也就是说,遍历器对象本质就是一个指针对象
- 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员
- 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员
- 不断调用指针对象的next方法,直到它指向数据结构的结束位置
每次调用的时候,都会返回数据结构的当前成员的信息,具体来说,就是返回一个包含value和done属性的对象
- value:表示当前成员的值
- done:是一个布尔值,表示遍历是否结束
下面是一个模拟next方法返回值的例子:
var it=makeIterator(['a','b'])
console.log(it.next());
console.log(it.next());
console.log(it.next());
function makeIterator(array){
var nextIndex=0;
return{
next:function(){
return nextIndex < array.length ?
{value:array[nextIndex++],done:false}:
{value:undefined,done:true}
}
}
}
使用细节:
-
由于Iterator只是把接口的规格加到数据结构上,所以,遍历器与所遍历的数据结构实际上是分开的,完全可以写出没有对应数据结构的遍历器对象
//下面是一个无限运行的遍历器对象的例子 var it=idMaker() it.next().value //'0' it.next().value //'1' it.next().value //'2' function idMaker(){ var index=0; return { next:function(){ return {value:index++,done:false} } } }
15.2 默认Iterator接口
15.2.1 概述
Iterator接口的目的是为所有数据结构提供一种统一的访问机制,即for…of循环。当使用for…of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口
数据结构只要部署了Iterator接口,我们就称这种数据结构为 “可遍历”的
默认的Iterator接口部署在数据结构的Symbol.iterator属性,一个数据结构只要具有Symbol.iterator属性,就可以认为是可遍历的
Symbol.iterator本身是一个表达式,返回Symbol对象的Iterator属性,这时一个预定义好的,类型为Symbol的特殊值,所以要放在方括号中
const obj={
[Symbol.iterator]:function(){
return {
next:function(){
return{
value:1,
done:true
}
}
}
}
}
上面的代码中,对象obj是可遍历的,因为其 具有Symbol.iterator属性。执行这个属性会返回一个遍历器对象,该对象的根本特征就是具有next方法
👨 一点思考:
一个对象要可遍历的话,要有Iterator接口,这个接口就是对象的Symbol.iterator属性,这个属性值是一个对象,这个对象中有next方法
原生具备Iterator接口数据结构如下:
- Array
- Map
- Set
- String
- TypedArray
- 函数的arguments对象
- NodeLIst
有了遍历器接口,数据结构就可以使用for…of循环遍历,也可以使用while循环遍历
15.2.2 对象实现遍历器接口
❓ 这些原生具备Iterator接口数据结构中,为什么没有对象呢?
😄 因为对象属性的遍历先后顺序是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口就等于部署一种线性转换,不过严格地说,对象部署遍历器接口并不是很有必要
如果一定要在对象上部署这个接口呢,我们有两种方法
1. 遍历器实现指针结构的例子:
function Obj(value){
this.value=value,
this.next=null
}
Obj.prototype[Symbol.iterator]=function(){
var iterator={next:next}
var current=this
function next(){
if(current){
var value=current.value
current=current.next
return{done:false,value}
}else{
return {done:true}
}
}
return iterator
}
var one=new Obj(1)
var two=new Obj(2)
var three=new Obj(3)
one.next=two
two.next=three
for(var i of one){
console.log(i)
}
上面的代码首先在构造函数的原型链上部署Symbol.iterator方法,调用该方法会返回遍历器对象iterator,调用该对象的next方法,在返回一个值的它是自动将内部指针移到下一个实例
👨 一点思考和理解:
- 什么叫指针结构,储存一个内存地址这个内存地址指向另一个变量或者数据
- 怎么来传递这个数据,是构造函数还是点运算符
- for…of会自动去寻找对象中的Symbol.iterator属性,并且这个属性要返回一个对象,这个对象中有一个next方法,会自动一直执行,直到返回值的done:true就不会再执行,在代码中,我创建了一个next属性给对象,并且使用这个属性达到对象的链表结构
2.添加Iterator接口的例子
let obj={
data:['hello','world'],
[Symbol.iterator](){
const self=this;
let index=0;
return{
next(){
if(index<self.data.length){
return{
value:self.data[index++],
done:false
}
}else{
return {value:undefined,done:true}
}
}
}
}
}
15.2.3 类似数组实现遍历器接口
对于类似数组的对象(存在数值键名和length属性),部署Iterator接口有一个简便方法,即使用Symbol.iterator方法直接引用数组的Iterator接口
let iterable={
0:'a',
1:'b',
2:'c',
length:3,
[Symbol.iterator]:Array.prototype[Symbol.iterator]
}
for(let item of iterable){
console.log(item) //'a','b','c'
}
❗️ 注意:普通对象部署数组的Symbol.iterator方法并无效果(就是键名不是数值的,例如字母的)
15.2.4 遍历器函数的返回值
如果Symbol.iterator方法对应的不是遍历器生成函数(即返回一个遍历器对象),解释引擎就会报错
var obj={}
obj[Symbol.iterator]=()=>1
[...obj] //TypeError:[] is not a function
15.3 调用Iterator接口的场合
- 解构赋值 :对数组和Set结构进行解构赋值时,会默认调用Symbol.iterator方法
- 扩展运算符:
- yield*:后面跟一个可遍历的结构,它会调用该结构的遍历器接口
- 其他场合:由于数组的遍历会调用遍历器接口,所以任何解释数组作为参数的场合其实都调用了遍历器的接口
- for…of
- Array.from()
- Map(),Set(),WeakMap(),WeakMap()
- Prmoise.add()
- Prmose.race()
15.4 遍历器对象的return(),throw()
遍历器对象除了具有next方法,还可以具有return方法和throw方法。如果自己写遍历器对象生成函数,那么next方法必须部署的,return方法和throw方法则是可选择部署的
15.4.1 return
return方法的使用场合是
- 如果for…of循环提前退出(通常因为出错,或者有break语句或continue语句)就会调用return方法
- 如果一个对象在完成遍历前需要清理或者释放资源,就可以部署return方法
function readLinesSync(file){
return{
next(){
return {done:true}
},
return(){
file.close();
return{done:true}
}
}
}
下面,我们让文件的遍历提前返回,这样就会触发执行return方法
for(let line of readLinesSync(fileName)){
console.log(line)
break;
}
❗️ 注意:return方法必须返回一个对象
15.5 for…of()循环
for…of可以使用的范围包括以下几种
- 数组
- Set
- Map
- 某些类似数组的对象(arguments对象,DOMNodeList对象)
- Generator对象
- 字符串
15.6 for…of()循环与其他遍历语法的比较
1 for循环
👍 优点:可以遍历
❌ 缺点:写法麻烦
2 forEach
👍 优点:相比for循环来说,写法简单多了
❌ 缺点:无法中途跳出循环,break命令或return命令都不能奏效
3.for…in
👍 优点:相比forEach来说,是可以跳出循环
❌ 缺点:
- 数组的键名是数组,但是for…in循环是以字符串作为键名的,’0‘,’1‘等
- for…in 循环不仅可以遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链的某一个键
- 某些情况下,for…in循环会以任意顺序遍历键名
总之:for…in循环主要是为遍历对象设计的,不适用于数组
4. for…of
👍 优点:
- 有着简洁的语法,并且没有for…in缺点
- 不同于forEach方法,它可以与breack continue和rerurn配合
- 提供遍历了所有数据结构的统一接口