理解迭代
在JavaScript中,计数循环是一种最简单的迭代。因为它可以指定迭代次数、每次迭代要执行什么操作;而且每次循环都在下一次迭代开始前完成、每次迭代的顺序都是事先定义好的:
for(let i=1;i<=10;i++){
console.log(i)
}
迭代会在一个有序集合上进行。“有序”即集合中所有项都可以按照既定顺序被遍历到,数组就是一个有序集合。由于数组有已知长度,且每一项都能通过索引获取,但这种方式并不适用于所有数据类型:
let arr=["blue","yellow","green"]
for(let i=0;i<arr.length;index++){
console.log(arr[i])
}
迭代器模式
迭代器模式是一个方案。即把实现了iterable接口的结构称为可迭代对象(iterrable),它们能创建迭代器iterator。
实现iterable接口
一个数据结构想要实现iterable接口,就必须具有Symbol.iterator
属性。即一个数据结构只要部署了Symbol.iterator
属性,就可以认为是“可迭代的”:
let num=1
let obj={}
let str="abc"
let arr=[1,2,3]
num[Symbol.iterator] //undefined
obj[Symbol.iterator] //undefined
str[Symbol.iterator] //f values() {native code}
arr[Symbol.iterator] //f values() {native code}
上面的例子表明:数值和对象是不可迭代的,即没有实现iterable接口。而字符串和数组是实现了iterable接口的。Symbol.iterator
是迭代器的工厂方法,只要调用它就能生成一个迭代器:
str[Symbol.iterator]() //StringIterator{}
arr[Symbol.iterator]() //ArrayIterator{}
实现了iterable接口的数据结构还有:映射Map、集合Set、arguments对象、NodeList等DOM集合类型
实际写代码过程中,不需要显式调用这个函数来生成迭代器,只要实现了iterable接口就可以运用如下操作:
for-of
循环- 数组解构
- 扩展运算符
- Array.From()
- 创建集合(Set)
- 创建映射(Map)
- Promise.all()接收由promise组成的可迭代对象
- Promise.race()接收由promise组成的可迭代对象
- yield*操作符
let arr=["green","red","yellow"]
//for-of循环
for (let el of arr){
console.log(el)
}
//数组解构
let [a,b,c]=arr
console.log(a,b,c) //green,red,yellow
//创建集合
let set=new Set(arr)
如果对象原型链上的父类实现了iterable接口,那这个对象也会实现这个接口
迭代器API
迭代器是一次性使用的对象,用于迭代与其相关联的可迭代对象。迭代器APInext()
用于在可迭代对象中遍历数据,以知晓迭代器当前位置。每次调用都会返回一个IteratorResult对象。IteratorResult对象包含两个属性:
done
:布尔值,判断是否完成遍历过程。ture表示完成value
:表示可迭代对象的下一个值,没有则返回undefined
//可迭代对象
let arr=["apple","banana"]
//迭代器
let iter=arr[Symbol.iterator]()
//执行迭代
iter.next() //{"value":"apple","done":false}
iter.next() //{"value":"banana","done":false}
iter.next() //{"value":"undefined","done":true}
iter.next() //{"value":"undefined","done":true}
不同迭代器的实例相互之间没有联系,只会单独遍历可迭代对象:
let arr=["apple","banana"]
//迭代器
let iter1=arr[Symbol.iterator]()
let iter2=arr[Symbol.iterator]()
// 执行迭代
iter1.next() //{"value":"apple","done":false}
iter2.next() //{"value":"apple","done":false}
iter2.next() //{"value":"banana","done":false}
iter1.next() //{"value":"banana","done":false}
如果可迭代对象在迭代期间被修改了,那么迭代器也会反映相应变化。另外,迭代器维护着一个指向可迭代对象的引用,因此迭代器会阻止垃圾回收程序回收可迭代对象
自定义迭代器
除了原生就具备实现iterable接口的数据类型,我们还可以让其他类型实现iterable接口:
class Counter {
constructor(limit){
this.limit=limit;
}
[Symbol.iterator](){
let count=1,limit=this.limit
return {
next(){
if(count<=limit){
return {done:false,value:count++}
}else{
return {done:true,value:undefined}
}
}
}
}
}
let counter=new Counter(3)
for(let count of counter){
console.log(count)
}
我们还可以覆盖原生的Symbol.iterator方法:
// 原生
let str="hello"
[...str] //["h","e","l","l","o"]
let str = new String("hi");
str[Symbol.iterator] = function() {
return {
next: function() {
if (this._first) {
this._first = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_first: true
};
};
//由于修改,扩展运算符返回的值变成了bye,而字符串本身还是hello
[...str] //["bye"]
str // "hello"
提前终止迭代器
可选的retuen()
方法用于指定迭代器在提前关闭时执行的操作。以下情况可以提前关闭迭代器:
for-of
循环通过break、continue、return或throw可提前退出- 解构操作没有消费所有值
return()
必须返回一个有效的IteratorResult对象,简单情况下可直接返回{done:true}
class Counter {
constructor(limit){
this.limit=limit;
}
[Symbol.iterator](){
let count=1,limit=this.limit
return {
next(){
if(count<=limit){
return {done:false,value:count++}
}else{
return {done:true,value:undefined}
}
},
//定义
return(){
console.log('提前关闭迭代器')
return {done:true}
}
}
}
}
let counter1=new Counter(5)
for(let count of counter1){
if(count>3){
break
}
console.log(count)
}
//1
//2
//3
//提前关闭迭代器
let counter2=new Counter(3)
let [a,b]=counter2
//提前关闭迭代器
由于return()
是可选的,所以并非所有迭代器都是可关闭的。仅仅给一个不可关闭的迭代器增加return()
并不能把它变成可关闭,这是因为调用return()
不会强制迭代器进入关闭状态。