美文网首页ES6
生成器和迭代器

生成器和迭代器

作者: 咸鱼不咸_123 | 来源:发表于2022-05-19 10:27 被阅读0次

    一、迭代器

    1.什么是迭代器

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

    • 其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中。
    • 在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等。

    从迭代器的定义我们可以看出来,迭代器是帮助我们对某个数据结构进行遍历的对象。

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

    • 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式;
    • 那么在js中这个标准就是一个特定的next方法。

    next方法有如下的要求:

    • 一个无参数或一个参数的函数,返回一个应当拥有以下两个属性的对象:
      • done(boolean)
        • 如果迭代器可以产生序列中的下一个值,则为false。(这等价于没有指定done这个属性。)
        • 如果迭代器已将序列迭代完毕,则为true。这种情况下,value是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
      • value
        • 迭代器返回的任何javascript值。done为true时可省略。
    const names=["abc","cba","nba"];
    
    
    // * 创建一个迭代器对象来访问数组
    let index=0;
    const namesIterator={
      next:function(){
        // return {done:false,"abc"};
        // return {done:false,"cba"};
        // return {done:false,"nba"};
        // return {done:true,undefined};
        if(index<names.length){
          return {value:names[index++],done:false};
        }else{
          return {value:undefined,done:true};
        }
      }
    }
    console.log(namesIterator.next());
    console.log(namesIterator.next());
    console.log(namesIterator.next());
    console.log(namesIterator.next());
    

    1.1 生成迭代器的函数

    // * 其实数组本身包含迭代器,只是我们想要实现这个思想
    const names=["wjy","hyz","tqy"];
    const nums=[1,2,3,4,5]
    
    // * 这些都有限迭代器
    function createArrayIterator(arr){
      let index=0;
      let iterator={
        index:0,
        next(){
          if(index<arr.length){
            return {done:false,value:arr[index++]}
          }else{
            return {done:true,value:undefined};
          }
        }
      }
      return iterator;
    }
    
    let namesIterator=createArrayIterator(names);
    let numsIterator=createArrayIterator(nums);
    console.log(namesIterator.next());
    console.log(namesIterator.next());
    console.log(namesIterator.next());
    console.log(namesIterator.next());
    
    console.log("-----分割线------------");
    console.log(numsIterator.next());
    console.log(numsIterator.next());
    console.log(numsIterator.next());
    console.log(numsIterator.next());
    console.log(numsIterator.next());
    
    console.log("----------无限迭代器--------");
    
    // * 创建一个无限的迭代器
    function createNumberIterator()
    {
      let index=0;
      return {
        next:function(){
          return {done:false,value:index++}
        }
      }
    }
    
    const numberIterator=createNumberIterator();
    console.log(numberIterator.next());
    
    

    2.可迭代对象

    但是上面的代码整体来说有点奇怪的:

    • 我们获取一个数组的时候,我们需要自己去创建一个index变量,再创建一个所谓的迭代器对象
    • 事实上我们可以对上面的代码进行进一步的封装,让其变成一个可迭代对象

    什么是可迭代对象呢?

    • 它和迭代器是不同的概念
    • 当一个对象实现了iterable protocol协议时,它就是一个可迭代对象
    • 这个对象的要求是必须实现@@iterator方法,在代码中我们使用Symbol.iterator访问该属性。

    那我们要问一个问题,我们转成这样的一个东西有什么好处呢?

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

    每次调用都是生成一个新的迭代器

    // *可迭代对象是符合iterable protocol协议,它是一个对象,
    // * 这个对象实现了@@iterator,在代码中我们可以使用Symbol.iterator去访问该属性,这个属性本身是一个函数,返回一个迭代器
    
    const iterableObj={
     names:["wjy","hyz","tqy"],
     [Symbol.iterator]:function(){
       let index=0;
       return {
        next:()=>{
          if(index<this.names.length){
            return {done:false,value:this.names[index++]}
          }else{
            return {done:true,value:undefined};
          }
        }
      }
     }
    }
    // * iterableObj就是一个可迭代的对象
    
    // * 生成了一个迭代器,每次调用都是产生了一个新的迭代器对象
    let iterator=iterableObj[Symbol.iterator]();
    
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    
    console.log("-------------分割线-------");
    
    let iterator2=iterableObj[Symbol.iterator]();
    
    console.log(iterator2.next());
    console.log(iterator2.next());
    console.log(iterator2.next());
    

    2.1 for...of只遍历可迭代的对象

    for...of只遍历可迭代的对象,例如下面的变量obj就是一个普通的变量,不是一个可迭代对象,所以会报错。

    // * for……of可以遍历的东西必须是一个可迭代的对象
    const obj={
      name:"wjy",
      age:18
    }
    
    // * 普通的对象是不支持for……of的,因为它不是一个可迭代对象
    for(const item of obj){
      console.log("item:",item);
    }
    

    使用for...of遍历iterableObj对象,它会调用@@iterator方法,那它什么时候会停止呢?

    当done为true时,就会停止。

    每次取的是value的值。

    const iterableObj={
     names:["wjy","hyz","tqy"],
     [Symbol.iterator]:function(){
       let index=0;
       return {
        next:()=>{
          if(index<this.names.length){
            return {done:false,value:this.names[index++]}
          }else{
            return {done:true,value:undefined};
          }
        }
      }
     }
    }
    
    for(const item of iterableObj){
      console.log("item:",item);
    }
    

    3.原生迭代器对象

    事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的:

    • String、Array、Map、Set、arguments对象、NodeList集合
      • 它们的实例对象就是一个可迭代对象
      • 函数的arguments也是一个可迭代对象
    //  * Array、String、Map、Set、NodeList的实例对象就是一个可迭代对象
    const names=["wjy","hyz","tqy"];
    
    // const iterator=names[Symbol.iterator]();
    // console.log(iterator.next());
    // console.log(iterator.next());
    // console.log(iterator.next());
    // console.log(iterator.next());
    
    for(const item of names){
      console.log(item);
    }
    
    console.log("-----------分割线------------");
    
    // * Set/Map
    const set=new Set();
    set.add("java");
    set.add("python");
    set.add("javascript");
    
    console.log(set[Symbol.iterator]);
    for(const item of set){
      console.log(item);
    }
    console.log("----------函数的arguements----------");
    
    // * 函数的arguments也是一个可迭代的对象
    function foo(x,y,z){
      console.log(arguments[Symbol.iterator]);
      for(const item of arguments){
        console.log("参数:",item);
      }
    }
    
    foo(10,20,30,50)
    

    4.可迭代对象的应用

    那么这些东西可以被用在哪里呢?

    • javascript中语法: for……of、展开语法、yield、解构赋值
    • 创建一些对象:new Map([iterable])、new WeakMap([iterable])、new Set([Iierable])、new Set([iterable])、new WeakSet([iterable])
    • 一些方法的调用:Promise.all([iterable])、Promise.race(iterable)、Array.from([iterable])

    这些方法本质上是调用迭代器的next方法

    const iterableObj={
      names:["wjy","hyz","tqy"],
      [Symbol.iterator]:function(){
        let index=0;
        return {
         next:()=>{
           if(index<this.names.length){
             return {done:false,value:this.names[index++]}
           }else{
             return {done:true,value:undefined};
           }
         }
       }
      }
     }
    
    //  * 展开运算符
     const names=["wjy","hyz","tqy"];
     const newNames=[...names,...iterableObj];
     console.log(newNames);
    
    
     const obj={
       name:"ysc",
       age:20
     }
    //  * ES9 新增的一个特性:用的不是一个迭代器
     const newObj={...obj};
     console.log(newObj);
    
     /**
      * * 解构
      */
    
     const[name1,name2]=names; // * 使用的是iterator.next()方法取出值
     const {name,age}=obj;//* 不一样,使用的ES9新增的特性。
    
    // * 创建一些其他对象时
    const set=new Set(iterableObj);
    const set2=new Set(names);
    console.log(set);
    console.log(set2);
    
    /**
     * * 数组 Array.from([iterable])
     */
    const arr1=Array.from(set);
    console.log("arr1:",arr1);
    
    /**
     * * Promise
     */
    // * 如果可迭代对象中的value是一个普通的值,而不是一个Promise,其实实际执行的是Promise.resolve(value)
    Promise.all(iterableObj).then(res=>{
      console.log(res);
    })
    
    • forEach是数组的方法

    5. 自定义类的迭代

    在前面我们可以看到Array、Set、Map、等类创建出来的对象都是可迭代对象

    • 在面向对象开发中,我们可以通过class类定义一个自己的类,这个类可以创建很多对象
    • 如果我们希望自己的类创建出来的对象默认都是可迭代的,那么在设计类的时候我们就可以添加上@@iterator方法

    案例:创建一个calssroom方法

    • 教室中有自己的位置、名称、当前教室的学生
    • 这个教室可以进来新学生(push)
    • 创建的教室对象是可迭代对象
    
    
    // * 创建一个classroom类,这个类创建出来的对象都是可迭代对象
    class Classroom{
      constructor(address,name,students){
        this.address=address;
        this.name=name;
        this.students=students;
      }
      entry(newStudent){
        this.students.push(newStudent)
      }
      [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,value:undefined}
          }
        }
      }
    }
    
    const classroom=new Classroom("厚德楼A栋","311",["james","kobe","curry","why"]);
    classroom.entry("lilei")
    for(const item of classroom){
      console.log(item);
      if(item=="james") break;
    }
    

    5.1 迭代器的中断

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

    • 比如遍历的过程中通过break、continue、return、throw中断了循环操作
    • 比如在解构的时候,没有解构所有的值

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

    image-20220516203348619.png

    二、生成器

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

    • 平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。

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

      • 首先,生成器函数需要在function的后面添加一个符号 : *
      • 其次,生成器函数可以通过yield关键字来控制函数的执行流程。
      • 最后,生成器函数的返回值是一个Generator(生成器)
        • 生成器事实上是一种特殊的迭代器
        • MDN:Instead ,they return a special type of iterator,called a Generator
    /**
     * * 生成器函数是一个函数,但和普通函数有一些区别
     * * 1.需要在function加 *
     * * 2. 可以使用yield关键字控制函数的执行流程
     * * 3. 生成器函数返回的其实是一个生成器(Generator)
     */
    function* foo(){
      console.log("函数开始执行");
      const value1=100;
      console.log(value1);
      yield
      const value2=200;
      console.log(value2);
      yield
      const value3=300;
      console.log(value3);
      yield
      console.log("函数执行结束");
    }
    
    let g=foo();//* 执行生成器函数时,其实里面的一行代码都不会执行,所以不会有任何的打印和输出,但会返回一个生成器
    console.log(g);
    // * 执行第一段代码
    g.next()
    console.log("----------分割线--------");
    // * 执行第二段代码
    g.next()
    console.log("----------分割线--------");
    
    // * 执行第三段代码
    g.next()
    console.log("----------分割线--------");
    
    // * 执行第四段代码
    g.next()
    

    1.生成器函数的执行

    • 生成器函数的执行体不会执行,但是会返回一个生成器对象。
      • 那么如何执行让它执行函数中的东西呢?调用next即可。
      • 我们之前学习迭代器时,知道迭代器的next是会有返回值的。
      • 但是我们很多时候不希望next返回的是一个undefined,这个时候我们可以通过yield来返回结果。
    /**
     * * 当遇到yield的时候,是暂停函数的执行
     * * 当遇到return的时候,是结束函数的执行,将done变为true,如果后面有代码都不会执行。
     */
    
    /**
     * * yield后面可以添加一个表达式,后面作为本次next的value的值
     */
    function* foo(){
      console.log("函数开始执行");
      const value1=100;
      console.log(value1);
      // return value1;//* return是一个特殊的yield,它就会将done变为true,后面的代码都不会执行了。
      // console.log("继续执行代码");
      yield value1;
      const value2=200;
      console.log(value2);
      yield value2;
      const value3=300;
      console.log(value3);
      yield value3
      console.log("函数执行结束");
      return "123"
    }
    
    
    /**
     * * 生成器是一个特殊的迭代器
     *  * next()函数会返回一个对象 {value:"",done:false/true}
     */
    const generator=foo()
    console.log("返回值1:",generator.next());
    console.log("返回值2:",generator.next());
    console.log("返回值3:",generator.next());
    console.log("返回值4:",generator.next());
    

    yield和return的区别:

    • yield会暂停函数的执行
    • return是终止函数的执行,并将done设置为true,后面的代码都不会执行。

    2. next方法传递参数

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

    • 答案是可以的。

    • next方法可以传递参数,它传递的值会作为上一个yield的返回值。

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

    function* foo(){
      console.log("函数开始执行");
      const value1=100;
      console.log(value1);
      const n=yield value1;
      
      const value2=200*n;
      console.log(value2);
      const count=yield value2;
    
      const value3=300*count;
      console.log(value3);
      yield value3;
    
      console.log("函数执行结束");
      return "123"
    }
    
    const generator=foo();
    
    // * 生成器的next可以传递参数
    // * next()传入的参数会作为上一个yield的返回值
    console.log(generator.next());
    
    // * 第二段代码的执行是第二次next的执行
    console.log(generator.next(10));
    
    // * 第三段代码的执行是第三次next的执行
    console.log(generator.next(20));
    

    3. return方法终止执行

    还可以调用return方法:它会终止函数的执行,将传递的参数作为value的值,done设置为true

    之后调用next不会继承生成值了,只会生成 {value:undefined,done:true}

    function* foo(){
      console.log("函数开始执行");
      const value1=100;
      console.log("第一段代码:",value1);
      const n=yield value1;
      
      const value2=200*n;
      console.log("第二段代码:",value2);
      const count=yield value2;
    
      const value3=300*count;
      console.log("第三段代码:",value3);
      yield value3;
    
      console.log("函数执行结束");
      return "123"
    }
    
    const generator=foo();
    console.log(generator.next());
    
    // * 第二段代码执行:使用了return:终止函数执行
    // * 那么就意味着相当于在第一段代码的后面添加了 return 参数,执行了return操作会将参数作为value的值,done设置为true
    // * 后面的代码都不会执行了。
    console.log(generator.return(10));
    
    image-20220516220727626.png

    4.trow方法抛出异常

    function* foo(){
      console.log("代码开始执行~");
       const  value1=100;
      try{
        yield value1;
      }catch(err){
        console.log("捕获到异常情况:",err);
        yield "abc"
      }
    
       const value2=200;
       yield value2; 
    
      console.log("代码执行结束~");
    }
    const generator=foo()
    
    const result=generator.next();
    // * 第二段代码不会执行 
    if(result.value!==200){
      console.log(generator.throw("error message"));
    }
    // * 如果你将异常捕获,代码可以继续执行,如果没有捕获,则是不可以的。
    

    5. 生成器替代迭代器

    因为生产器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器替代迭代器。

    • 事实上我们还可以使用yield*来生产一个可迭代对象
      • 这个时候相当于yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值。
    function* createArrayIterator(arr){
      
      // * 第一种写法
      // yield "wjy";
      // yield "hyz";
      // yield "tqy";
    
      // * 第二种写法
      // for(const item of arr){
      //   yield item;
      // }
    
      // * 第三种写法 yield*,后面跟一个可迭代的对象
      yield* arr;
    
    
    }
    
    const names=["wjy","hyz","tqy"];
    const namesIterator=createArrayIterator(names);
    console.log(namesIterator.next());
    console.log(namesIterator.next());
    console.log(namesIterator.next());
    console.log(namesIterator.next());
    
    /**
     * * yield * 
     */
    

    创建一个函数,这个函数可以遍历一个范围内的数字

    // * 2. 创建一个函数,这个函数可以迭代一个范围内的数字
    function* createRangeIterator(start,end){
      let index=start;
      while(index<end){
        yield index++;
      }
      // let index=start;
      // return {
      //   next:function(){
      //     if(start<end){
      //       return {done:false,value:start++};
      //     }else{
      //       return {done:true,value:undefined}
      //     }
      //   }
      // }
    }
    let  rangeIterator=createRangeIterator(2,10);
    console.log(rangeIterator.next());
    console.log(rangeIterator.next());
    console.log(rangeIterator.next());
    console.log(rangeIterator.next());
    console.log(rangeIterator.next());
    console.log(rangeIterator.next());
    console.log(rangeIterator.next());
    console.log(rangeIterator.next());
    console.log(rangeIterator.next());
    

    Classroom的Symbol.iterator方法改写,里面使用生成器来完成迭代。

    // * classroom案例
    
    class Classroom{
      constructor(address,name,students){
        this.address=address;
        this.name=name;
        this.students=students;
      }
      entry(newStudent){
        this.students.push(newStudent)
      }
     foo=()=>{
       console.log("foo function");
     }
    //  [Symbol.iterator]=function*(){
    //   yield* this.students;
    //  }
      *[Symbol.iterator](){
      yield* this.students;
      }
    } 
    
    const classroom=new Classroom("厚德楼","311",["hyz","wjy"])
    // const classroomIterator=classroom[Symbol.iterator]();
    // console.log(classroomIterator.next());
    
    for(const item of classroom){
      console.log(item);
    }
    

    6.异步处理方案

    function requestData(url){
      // 模拟网络请求
      return new Promise((resolve,reject)=>{
        setTimeout(()=>{
          resolve(url);
        },3000);
      })
    }
    

    需求:

    // * 多次调用
    // * 1.url:why->res:why
    // * 2.url:res+"aaa"->res:whyaaa
    // * 3.url: res+"bbb"=>res:whyaaabbb
    

    6.1 回调地狱

    // * 回调函数嵌套回调函数,回调地狱,代码可读性差
    // * 第一种方案:多次回调
    requestData("why").then(res=>{
      requestData(res+"aaa").then(res=>{
        requestData(res+"bbb").then(res=>{
          console.log("最终:",res);
        })
      })
    })
    

    6.2 Promise.then的返回值来实现

    • 可读性差
    requestData("why").then(res=>{
      return requestData(res+"aaa");
    }).then(res=>{
      return requestData(res+"bbb");
    }).then(res=>{
      console.log("最终:",res);
    })
    

    6.3 Promise和generator的实现

    function* getData(){ //* 封装成一个生成器函数
      const res1=yield requestData("why");
      const res2=yield requestData(res1+"aaa");
      const res3=yield requestData(res2+"bbb");
      console.log(res3);
    }
    // * 手动执行生成器函数
    const generator=getData();//* 生成器函数返回一个生成器
    generator.next().value.then(res=>{
      generator.next(res).value.then(res=>{
        generator.next(res).value.then(res=>{
          console.log(res);
        })
      })
    })
    

    改写了一个自动执行生成器函数:

    function exeGenerator(genFn){
      const generator=genFn();
      function exec(res){
        const result=generator.next(res);
        if(result.done) return result.value;
        result.value.then(res=>{
          // console.log(res);
          exec(res);
        })
      }
      exec();
    }
    exeGenerator(getData)
    

    或使用第三方库:co

    // * 第三方包co自动执行
    const co=require("co");
    co(getData)
    

    6.4 async和await

    // * 第四种方案: async/await:本质上是generator和Promise的语法糖
    async function getData(){ //* 封装成一个生成器函数
      const res1=await requestData("why");
      const res2=await requestData(res1+"bbb");
      const res3=await requestData(res2+"ccc");
      console.log(res3);
    }
    getData()
    

    三、总结

    生成器和迭代器.png

    相关文章

      网友评论

        本文标题:生成器和迭代器

        本文链接:https://www.haomeiwen.com/subject/ioftprtx.html