• JavaScript的迭代器与生成器


    迭代器

    什么是迭代器?

    迭代器(iterator),是确使用户可在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关系对象的内部实现细节。

    其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中;

    在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java,Python等;
    在迭代器的定义我们可以看出来,迭代器是帮助我们对某个数据结构进行遍历的对象

    在Javascript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol);

    迭代器协议定义了产生一系列值(无论是有限还是无限个) 的标准方式;

    那么在js中这个标准就是一个特定的next方法;

    next方法有如下的要求:
    一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:

    done(boolean)

    • 如果迭代器可以产生序列中的下一个值,则为false。(这等价于没有指定done这个属性)
    • 如果迭代器已将序列迭代完毕,则为true。这种情况下,value是可选的,如果它依然存在,即为迭代结束之后的默认返回值。

    value

    迭代器返回的任何JavaScript值。done为true时可省略。
    在这里插入图片描述

    const iterator = {
    	//有next
        next:function (){
       	 //有返回值
            return  {
                done:true,
                value:'aaa'
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    迭代器的代码练习:

    // 数组
    const array = ['abc', 'bcd', 'dba']
    
    
    // 创建一个迭代器来遍历数组
    let index = 0
    const namesIterator = {
        next() {
    
            if(index < array.length){
                return { done:false, value:array[index++] }
            }else{
                return{ done :true,value :undefined }
            }
        }
    }
    console.log(namesIterator.next())
    console.log(namesIterator.next())
    console.log(namesIterator.next())
    console.log(namesIterator.next())
    console.log(namesIterator.next())
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    结果:

    在这里插入图片描述

    可迭代对象

    它和当前是不同的概念;

    当一个对象实现了iterable protocol协议(可迭代协议)时,它就是一个可迭代对象;

    这个对象的要求是必须实现 @@iterator方法,在代码中我们使用Symbol.iterator访问该属性;

    那么可迭代对象有什么好处呢?

    当一个对象变成一个可迭代对象时,进行某些迭代操作,比如for…of操作时,其实就会调用它的@@iterator方法

    可迭代对象代码:

    const iteratorObj = {
        names: ['abc', 'cef', 'fed'],
        [Symbol.iterator]() {
            let index = 0
            return {
                next:() => {
                    if (index < this.names.length) {
                        return { done: false, value: this.names[index++] }
                    } else {
                        return { done: true, value: undefined }
                    }
                }
            }
        }
    }
    const iterator = iteratorObj[Symbol.iterator]()
    console.log(iterator.next())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    当我们用for…of方法去遍历上面这段代码时(可迭代对象)就会遍历出来

    for(let iterators of iteratorObj) {
        console.log(iterators) //可以使用for ... of()
    }
    
    • 1
    • 2
    • 3

    结果:在这里插入图片描述

    而我们对普通对象进行for…of遍历时是会报错的

    // for...of 
    const obj = {
        name:'ljb',
        age:18
    }
    
    // 这里的obj就不能使用for..of 因为它不是一个可迭代对象
    for(let item of obj) {
        console.log(item)   // 报错:TypeError: obj is not iterable (可迭代的)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    原生迭代器对象

    JS本身就有内置的迭代器对象:

    String、Array、Map、Set、arguments对象,NodeList集合;

    const names = ['abc', 'cdf', 'fgd'] //字面量
    
    for(let item of names ){
        console.log(item)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    可迭代对象的应用

    1.Javascript中语法:for … of 、展开语法(spread syntax) 、yield*、解构赋值(Destructuring_assignment);

    2.创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);

    3.一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);

    const iteratorObj = {
        names: ['abc', 'dbc', 'def'],
        [Symbol.iterator]() {
            let index = 0
            return {
                next: () => {
                    if (index < this.names.length) {
                        return { done: false, value: this.names[index++] }
                    } else {
                        return { done: true, value: undefined }
                    }
                }
            }
        }
    }
    
    const arr = [10, 20, 30, 40, 40]
    const newArr = [...arr,...iteratorObj]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    自定义类的迭代

    在上面Array,Set、String、Map等类创建出来的对象都是可迭代对象

    在面向对象开发中,我们可以通过class定义一个自己的类,这个类可以创建很多的对象

    如果我们也希望自己的类创建出来的对象默认是可迭代的,那么在设计类的时候我们可以添加上@@iterator方法;

    class Classroom {
        constructor(adddress, name, students) {
            this.adddress = adddress
            this.name = name
            this.students = students
        }
        [Symbol.iterator](){
            let index = 0
            return {
                next:() =>{
                    if(index<this.students.length){
                        return { done :false,value:this.students[index++] }
                    }else{
                        return { done:true ,value:undefined }
                    }
                }
            }
        }
    }
    
    const classroom = new Classroom('重庆', '计算机教室', ['jack', 'Tom', 'curry'])
    
    for(let item of classroom) {
        console.log(item)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    迭代器的中断

    迭代器在某些情况下会在没有完全迭代的情况下中断:

    比如遍历的过程中通过break、continue、return、throw中断了循环操作;

    比如在解构的时候,没有解构所有的值;

    这个时候我们想要监听中断的话,可以添加return方法:

        [Symbol.iterator](){
            let index = 0
            return {
                next:() =>{
                    if(index<this.students.length){
                        return { done :false,value:this.students[index++] }
                    }else{
                        return { done:true ,value:undefined }
                    }
                },
                //迭代器的中断
                return() {
                    console.log('迭代器提前终止了')
                    return { done :true }
                }
            }
        }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    for(let item of classroom) {
        console.log(item)
        if(item === 'Tom') break
    }
    
    • 1
    • 2
    • 3
    • 4

    结果:

    在这里插入图片描述

    生成器

    什么是生成器?

    生成器是ES6中新增的一种函数控制,使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。

    生成器函数也是一个函数,但是和普通函数有一些区别:

    首先,生成器函数需要在function的后面加一个符号:*

    其次,生成器函数可以通过yield关键字来控制函数的执行流程:

    最后,生成器函数的返回值是一个Generator(生成器):

    • 生成器事实上是一种特殊的迭代器;
    • MDN:Instead,they return a special type of iterator,called a Generator

    生成器函数执行

    我们如何让它执行函数中的东西呢?调用next即可;

    迭代器的next是会有返回值的

    但是我们很多时候不希望next返回的是一个undefined,这个时候我们就可以通过yield来返回结果:

    function* foo() {
        const value1 = 10
        console.log(value1)
        yield value1
        const value2 = 20 
        console.log(value2)
    
        const value3 = 30 
        console.log(value3)
        yield value2
        console.log('函数执行结束~')
    }
    const genertor = foo()  
    console.log(genertor.next())
    console.log(genertor.next())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    结果:

    生成器传递参数-next函数

    函数既然可以暂停来分段执行,那么函数应该是可以传递参数的,我们是否可以给每个分段来传递参数呢?

    那肯定是可以的;

    我们在调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值

    注意:也就是说我们是为本次的函数代码块执行提供了一个值;

    function* foo() {
        const value1 = 10
        console.log(value1)
        const n =  yield value1
        
        const value2 = 20  + n
        console.log(value2)
        yield value2 
    
        const value3 = 30 
        console.log(value3)
        yield value3
    
        console.log('函数执行结束~')
    }
    const genertor = foo()  
    
    console.log(genertor.next())
    
    console.log(genertor.next(100))
    console.log(genertor.next())
    console.log(genertor.next())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    生成器提前结束-return函数

    还有一个可以给生成器函数传递参数的方法就是提供return函数;

    return函数传值后这个生成器函数就会结束,之后调用next不会继续生成值了;

    function* foo() {
        const value1 = yield 'aaa'
        console.log('value1:',value1)
        const value2 = yield value1
        const value3 = yield value1
    }
    const generator = foo()
    console.log(generator.next())
    console.log(generator.return(123))
    console.log(generator.next())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    生成器替代迭代器

    我们发现生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来代替迭代器:

    // 生成器代替迭代器
    function* createArrayIterator(arr) {
        for(let item of arr) {
            yield item
        }
    
    }
    const names = ['abc', 'dbc', 'cdf']
    const namesIterator = createArrayIterator(names)
    console.log(namesIterator.next())
    console.log(namesIterator.next())
    console.log(namesIterator.next())
    console.log(namesIterator.next())
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    事实上我们还可以使用yield* 来生产一个可迭代对象:
    这个时候相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值

  • 相关阅读:
    【USRP】产品型号、参数、架构全解析系列 9:X410
    SpringCloud 微服务与远程调用测试
    stthjpv:一款针对JWT Payload的安全保护工具
    操作步骤:EasyCVR平台国标GB设备如何正确配置告警预案?
    远程监控电脑软件怎么选?
    在有springSecurity或者若依项目中获取当前系统登录的用户信息
    在openSUSE-Leap-15.5-DVD-x86_64中使用钉钉dingtalk_7.0.40.30829_amd64
    SkiaSharp 之 WPF 自绘 五环弹动球(案例版)
    【android开发-10】android中四种布局详细介绍
    java-BigDecimal 概述
  • 原文地址:https://blog.csdn.net/m0_62336865/article/details/125585842