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

什么是迭代

类似于遍历 遍历:有多个数据组成的集合数据结构(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 更常用于异步编程,但生成器在定制化迭代器协议、性能优化框架中仍不可替代
相关推荐
火星牛2 天前
SPA模式下的es6如何加快宿主页的显示速度
前端·ecmascript·es6
香蕉可乐荷包蛋4 天前
浅入ES5、ES6(ES2015)、ES2023(ES14)版本对比,及使用建议---ES6就够用(个人觉得)
前端·javascript·es6
卓律涤5 天前
【找工作系列①】【大四毕业】【复习】巩固JavaScript,了解ES6。
开发语言·前端·javascript·笔记·程序人生·职场和发展·es6
Watermelo61710 天前
前端如何应对精确数字运算?用BigNumber.js解决JavaScript原生Number类型在处理大数或高精度计算时的局限性
开发语言·前端·javascript·vue.js·前端框架·vue·es6
一个游离的指针11 天前
ES6基础特性
前端·javascript·es6
layman052811 天前
ES6/ES11知识点
前端·ecmascript·es6
全栈凯哥12 天前
ES6 (ECMAScript 2015) 详解
前端·ecmascript·es6
海盐泡泡龟13 天前
ES6新增Set、Map两种数据结构、WeakMap、WeakSet举例说明详细。(含DeepSeek讲解)
前端·数据结构·es6
Mr.闻吉安14 天前
什么是变量提升?
javascript·es6