Iterator(迭代器)
18888628835 opened this issue · 0 comments
Iterator(迭代器)
什么是Iterator
可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在 for..of 循环中使用的对象。
数组是可迭代的。但不仅仅是数组。很多其他内建对象也都是可迭代的。例如字符串也是可迭代的。
Iterator 就是针对那么多可迭代的数据结构中是一种统一的接口机制,只要部署了这个接口,就可以被依次遍历处理。
Iterator 的遍历过程是这样的:
- 首先会在内部创建一个指针,指向指针开始位置
- 当循环开始时,调用其内部的 next 方法
- 返回当前成员信息=>
{value:any,done:boolean}
- 第二步-第三步循环执行,直到指针指到最后位置
- 结束
手写 Iterator
下面我们通过一个例子快速了解核心概念。
需要知道的是,对象默认是没有 Iterator 接口的,所以它不能被 for..of 循环。这里我们手动给它设置 Iterator 接口。
let range={
from:0,
to:5
}
如果能让上面的range
对象通过for..of
循环打印出0、1、2、3、4、5。这就代表 iterator 接口部署成功了
1.首先我们要给它设置一个属性,这个属性只能通过Symbol.iterator访问,这个属性保存一个函数。
range[Symbol.iterator]=function(){}
这个 Symbol.iterator
是部署的开始,当 for..of 开始循环时,会调用Symbol.iterator
函数
2.Symbol.iterator
函数会返回一个对象,内部有一个 next 方法,并返回{value:xxx,done:boolean}
这样的对象。
// 由for...of 调用range[Symbol.iterator]()并返回 Iterator 对象
range[Symbol.iterator]=function(){
// 返回一个iterator 对象
return {
first:0,
last:5,
next(){
//这里的 this 指向iterator 对象,由 for..of 每次循环时自动调用该对象的 next 方法
if(this.first<=this.last){
return {value:this.first++,done:false}
}else{
return {done:true}
}
}
}
}
3.打印信息
for(let i of range){
alert(i)
}
会依次打出0-5的数,Iterator
接口部署成功了。
小结
如果需要部署 Iterator 接口,我们需要手动添加 Symbol.Iterator属性。
这个属性保存着一个函数,当 for..of 开始时,就会调用该函数,并返回Iterator 对象,此时指针初始化。
Iterator对象内部保存着一个 next 函数,for..of 的每次循环都会调用到Iterator对象中的next 函数,所以这时候的 this 指向 Iterator 对象。
next函数会返回{value:xx,done:boolean}
的数据结构。 当 done 为 true 时,循环停止。
for..of
循环每次都会取返回结果的value
属性
内置 Iterator
数组跟字符串内置了 Iterator 接口,所以我们可以直接使用 for..of来循环,比如下面的字符串
for(let str of 'strings'){
console.log(str)
}
// s t r i n g s
下面我们直接获取它的Symbol.iterator
属性,来看看到底发生了什么。
我们已经知道这个属性保存着一个函数,这个函数会返回 Iterator 对象,所以我们直接调用拿到这个对象。
let strings='strings'
let Iterator=strings[Symbol.iterator]()
这个对象内部会执行 next 方法,返回{value:xx,done:boolean}
对象
Iterator.next()
//{value: "s", done: false}
现在我们得出一个结论:可迭代对象就是内部部署了 Iterator接口的对象,这个部署 Iterator 接口的标志是内部有 [Symbol.iterator]
属性。
我们可以通过浏览器打印出来看一下,比如例子中的strings
的原型上就有[Symbol.iterator]
属性,它是一个函数。
我们直接调用这个方法,然后看看内部到底是个啥
可以看到返回的StringIterator 的原型上有个 next 方法。
伪数组问题
可迭代对象的概念就是实现[Symbol.iterator]
方法的对象
伪数组和数组一样,本质上是个对象,但是数组有数组原型(Array.prototype
),而伪数组没有数组原型。
通过上面的截图我们知道[Symbol.iterator]
会部署在原型上,比如数组原型、字符串原型里就有部署这个属性。
对象以及其原型是没有部署 Iterator
接口的。伪数组既然没有数组的原型,那就不一定有 Iterator 接口。
比如下面是一个伪数组
let arrayLike = { // 有索引和 length 属性 => 类数组对象
0: "Hello",
1: "World",
length: 2
};
它的原型直接连接到 Object.prototype
上,所以它没有数组的共用方法。比如 push
,pop
等等。
如果希望实现,那可以采用Array.from
。
Array.from(arrayLike)
这样这个伪数组就具备 Iterator 接口了。
有没有一种伪数组虽然没有数组的原型,但是却默认部署 Iterator 接口的呢?
有的,任何可以被扩展运算符变成数组的伪数组都有Iterator
接口。
比如arguments
虽然没有数组的原型,但是却具有 [Symbol.iterator]
接口
扩展运算符把伪数组变成数组?
也许你会认为扩展运算符能把所有伪数组变成数组,不过这只对了一半。
扩展运算符只能把已经具备 Iterator 接口的数据变成数组。不信我们看
结论就是只要你部署了Iterator 接口,才能用扩展运算符转化为数组。可不要弄反顺序噢。
总结
Iterator 是一个接口,具备 Iterator 接口的特点是内部有[Symbol.iterator]
属性。这个属性保存着一个会返回 Iterator 对象的函数。
Iterator对象内部具备 next 方法,会返回键名为 done 和 value 的对象。
for..of 遍历器为部署了 Iterator 接口的数据结构而生,当使用 for..of 遍历时,会首先调用[Symbol.iterator]
方法,生成Iterator
对象,每次循环都会调用这个对象内部的 next 方法。
有一些伪数组没有部署Iterator 接口,所以不能被扩展运算符转化为数组。这时候需要采用 Array.from
方法。
Symbol.iterator 方法会被 for..of 自动调用,但我们也可以直接调用它。
内置的可迭代对象例如字符串和数组,都实现了 Symbol.iterator。