《红宝书》 |迭代器

理解迭代

在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。
yc1VTe.png

实现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()不会强制迭代器进入关闭状态。

posted @ 2021-02-16 17:35  sanhuamao  阅读(90)  评论(0编辑  收藏  举报