迭代、迭代器、生成器的前世今生

什么是迭代

类似于遍历 遍历:有多个数据组成的集合数据结构(map、set、array等其他类数组),需要从该结构中依次取出数据进行某种处理。 迭代:按照某种逻辑,依次取出下一个数据进行处理。

什么是迭代器 iterator

JS语言规定,如果一个对象具有next方法,并且next方法满足一定的约束,则该对象是一个迭代器(iterator)。 next方法的约束:该方法必须返回一个对象,该对象至少具有两个属性:

  • value:any类型,下一个数据的值,如果done属性为true,通常,会将value设置为undefined
  • done:bool类型,是否已经迭代完成
  • 通过迭代器的next方法,可以依次取出数据,并可以根据返回的done属性,判定是否迭代结束。

案例如下:

复制代码
/**
       * 迭代器
       * 必须有个next函数
       * 该函数必须返回一个对象 包括 value 属性 和 done属性
       * value:本次迭代的返回值
       * done:true 或者 false 本次迭代是否结束
       * */
      const iterator = {
        total:3,
        index:1,
        next(){
          const obj = {
            value:this.index > this.total?undefined:Math.random(),
            done:this.index > this.total
          }
          this.index++;
          return obj;
        }
      }

      //一个一个迭代直到不能迭代为止

      let result = iterator.next();
      while(!result.done){
        console.log(result);
        result = iterator.next();
      }

      //输出斐波拉数列的数据
      //1 1 2 3 5 8 13 21
      const sequenceItereator = {
        a:1,
        b:1,
        curIndex:1, //当前从1开始算
        next(){
          if(this.curIndex == 1 || this.curIndex == 2){
            this.curIndex++;
            return {
              value:1,
              done:false
            }
          }
          var c = this.a + this.b;
          this.curIndex++;
          this.a = this.b;
          this.b = c;
          return {
            value:c,
            done:false
          }
        }
      }

      for(var i = 0;i<50;i++){
        console.log(sequenceItereator.next());
      }

什么是迭代器创建函数

它是指一个函数,调用该函数后,返回一个迭代器,则该函数称之为迭代器创建函数,可以简称位迭代器函数。

案例如下:

复制代码
//迭代器创建函数
      function createIterator(arr){
        var i = 0;
        return {
          next(){
            var obj = {
              value:arr[i],
              done:i>arr.length - 1
            }
            i++;
            return obj;
          }
        }
      }
      var iterator = createIterator([1,3,5,7,9]);
      console.log(iterator);

什么是可迭代协议

ES6中出现了for-of循环,该循环就是用于迭代某个对象的,因此,for-of循环要求对象必须是可迭代的(对象必须满足可迭代协议) 可迭代协议是用于约束一个对象的,如果一个对象满足下面的规范,则该对象满足可迭代协议,也称之为该对象是可以被迭代的。 可迭代协议的约束如下:

  • 对象必须有一个知名符号属性(Symbol.iterator)
  • done:bool类型,是否已经迭代完成
  • 该属性必须是一个无参的迭代器创建函数

案例如下:

复制代码
//可迭代协议
      var robj = {
        [Symbol.iterator]:function(){
          var total = 3;
          var i = 1;
          return {
            next(){
              var oop = {
                value:i>total?undefined:Math.random(),
                done:i>total
              }
              i++;
              return oop;
            }
          }
        }
      };
      for (const element of robj) {
        console.log(element);
      }

什么是生成器

生成器:由构造函数Generator创建的对象,该对象既是一个迭代器,同时,又是一个可迭代对象(满足可迭代协议的对象)

**注意:Generator构造函数,不提供给开发者使用,仅作为JS引擎内部使用**

生成器函数(生成器创建函数):该函数用于创建一个生成器。

ES6新增了一个特殊的函数,叫做生成器函数,只要在函数名与function关键字之间加上一个*号,则该函数会自动返回一个生成器

生成器函数的特点:

  • 调用生成器函数,会返回一个生成器,而不是执行函数体(因为,生成器函数的函数体执行,收到生成器控制)
  • 每当调用了生成器的next方法,生成器的函数体会从上一次yield的位置(或开始位置)运行到下一个yield
    • yield关键字只能在生成器内部使用,不可以在普通函数内部使用
    • 它表示暂停,并返回一个当前迭代的数据
    • 如果没有下一个yield,到了函数结束,则生成器的next方法得到的结果中的done为true
  • yield关键字后面的表达式返回的数据,会作为当前迭代的数据
  • 生成器函数的返回值会作最终的value的值 但是当在进行next时 value是undefined
  • 生成器在调用next的时候可以传递参数,该参数会作为上一次yield整个表达式的返回结果

案列如下:

复制代码
//生成器函数  调用该函数返回一个生成器  该生成器即使是一个迭代器 又是一个可迭代对象(满足可迭代协议)
      function* createGenerator(){
        console.log('函数体执行 - 开始');
        yield 1; //会作为本次迭代的value值 {value:1,done:false}
        console.log("函数体执行 - 1")
        yield 2;//会作为本次迭代的value值 {value:2,done:false}
        console.log('函数体执行 - 2');
        yield 3;//会作为本次迭代的value值 {value:3,done:false}
        console.log("函数体执行 - 3");
        return "结束"//会作为本次迭代的value值 {value:"结束",done:true}
      }
      //掉用只会返回一个生成器 不会执行函数体
      var generator = createGenerator();
      //当调用next的时候会从开始位置到第一个yield处执行  执行到yield位置就会卡住(不会继续执行), 等到下一次next的时候
      //生成器的函数体会从上一次yield的位置(或开始位置)运行到下一个yield
      console.log(generator.next);
      const iterator = generator[Symbol.iterator]();


      function* createArrayIterator(array){
        for (let index = 0; index < array.length; index++) {
          const item = array[index];
          console.log(`第${index}次迭代`);
          yield item;
        }
      }
      const arrayIterator = createArrayIterator([1,2,3,4,5,6]);
复制代码
//生成器函数  调用该函数返回一个生成器  该生成器即使是一个迭代器 又是一个可迭代对象(满足可迭代协议)
      function* createGenerator(){
        console.log('函数体执行 - 开始');
        let result = yield 1; //会作为本次迭代的value值 {value:1,done:false}
        console.log("函数体执行 - 1",result)
        result = yield 2;//会作为本次迭代的value值 {value:2,done:false}
        console.log('函数体执行 - 2',result);
        result = yield 3;//会作为本次迭代的value值 {value:3,done:false}
        console.log("函数体执行 - 3",result);
        return "结束"//会作为本次迭代的value值 {value:"结束",done:true}
      }
      let itereator = createGenerator();
      let res = itereator.next();
      function run(){
        if(!res.done){
          //如果在调用next的时候 给 next传递参数  该参数会作为 yield 整个表达式的值返回
          //执行步骤
          //第一次调用就不care了  第一次调用碰到yield 1; 就会卡住不会往下执行  这个时候还不执行赋值操作
          //第二次调用next 传递上一次迭代的值作为参数传递  这个时候 就会从上一次 yield的位置 运行到下一个yield (这个时候就会进行赋值操作)
          //整个yield 表达式的返回值 就是给next函数传递的参数
          //依次类推
          console.log(res);
          res = itereator.next("张三:"+Math.random());
          run();
        }
      }
      run();
复制代码
var i = 0;
      function asyncData(){
        return new Promise((resolve,reject)=>{
          setTimeout(() => {
            i++;
            //3秒后完成 完成的数据
            resolve('完成'+i);
          }, 10000);
        })
      }
      //调用next()方法时传入的值会作为上一个yield表达式的返回值
      //创建一个生成器函数 调用时返回一个生成器
      function* task(){
        console.log("开始获取数据");
        let data = yield asyncData();
        console.log('获取到的数据',data);
        data = yield asyncData();
        console.log("又获取到了数据",data);
        data = yield 1;
        console.log('又获取到了数据',data);
        return '结束';
      }
      //没封装之前的写法
      /*function run(createGenerator){
        const generator = createGenerator();
        let res = generator.next();
        function next(){
          if(!res.done){
            console.log(res);
            const value = res.value;
            if(typeof value.then === 'function'){
              value.then((data)=>{
                res = generator.next(data);
                next();
              });
            }else{
              res = generator.next(value);
              next();
            }
          }
        }
        next();
      }*/

      //封装后的写法
      function run(createGenerator){
        const generator = createGenerator();
        console.log(generator);
        next();

        function next(nextValue){
          const res = generator.next(nextValue);
          if(res.done){
            console.log('生成器迭代结束');
            return;
          }
          const value = res.value;
          if(typeof value.then === 'function'){
            //如果返回的是promise  将promise完成时的数据作为参数
            //作为上一次yield表达式的返回值
            value.then((data)=>{
              return next(data)
            })
          }else{
            console.log('走这里了')
            //将上一次迭代获取到的value的值作为参数 传递给上一次yield表达式的返回值
            next(res.value);
          }
        }
      }

      run(task);
复制代码
function* g2(){
        console.log('g2函数体-运行');
        let res = yield 'g1';
        console.log('g1运行');
        res = yield 'g2';
        console.log('g2运行')
        return 123;
      }

      function* createGenerator(){
          console.log('函数体-开始')
          let res = yield 1; //1作为 本次迭代的值 {value:1,done:false}
          console.log('函数体-运行1',res);
          res = yield* g2();
          console.log('函数体-g2',res);
          res = yield 2;
          console.log('函数体-运行2',res);
          res = yield 3;
          console.log('函数体-运行3',res);
          return '结束'
      }
      var generator = createGenerator();

总结:

生成器的核心价值在于其‌延迟执行与状态保持能力‌,适用于:

  • 需要按需生成数据的迭代场景(如分页、树遍历)
  • 资源敏感型任务(如大文件处理、流式传输)
  • 复杂流程控制(如多步骤交互、状态机)
  • 尽管 async/await 更常用于异步编程,但生成器在定制化迭代器协议、性能优化框架中仍不可替代
相关推荐
吴声子夜歌3 小时前
ES6——Calss详解
javascript·es6·原型模式
吴声子夜歌14 小时前
ES6——二进制数组详解
前端·ecmascript·es6
吴声子夜歌16 小时前
ES6——正则的扩展详解
前端·mysql·es6
吴声子夜歌18 小时前
ES6——Generator函数详解
前端·javascript·es6
吴声子夜歌18 小时前
ES6——Set和Map详解
前端·javascript·es6
吴声子夜歌1 天前
ES6——异步操作和async函数详解
前端·ecmascript·es6
吴声子夜歌2 天前
ES6——数组的扩展详解
前端·javascript·es6
吴声子夜歌2 天前
ES6——Iterator和for...of循环详解
前端·javascript·es6
吴声子夜歌2 天前
ES6——Symbol详解
开发语言·javascript·es6
来一颗砂糖橘4 天前
吃透 ES6 扩展运算符(...):从表面语法到底层逻辑,避开所有坑
前端·javascript·es6·扩展运算符·前端进阶