• 《红宝书》 |迭代器


    理解迭代

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

  • 相关阅读:
    好书推介《实战机器学*》
    Web技术图书名单
    大数据技术书,看看有没有感兴趣的
    博客园设置自定义皮肤,添加自定义小模块悬浮天气组件,github图标链接等
    Final Cut Pro 视频剪辑学习记录,快捷操作等
    css 利用 clip-path 裁剪多边形,三角形,梯形,六边形等
    有呀,有呀,设计!有呀,有呀,组件!
    github README添加badge标识,多彩的tag标签
    vue timeline 开箱即用的时间轴组件,日志更新时间轴组件
    那些需要收藏的网站网址
  • 原文地址:https://www.cnblogs.com/sanhuamao/p/14407044.html
Copyright © 2020-2023  润新知