JavaScript-迭代器与生成器

145 阅读4分钟

迭代器

首先先来理解一下什么是迭代器,从一个数据的集合中,不断地拿取出数据的过程。但是遍历似乎也是这样的。同时js中规定如果一个对象有next方法,并且返回一个{value: 值, done: 是否迭代完成}格式的代码,那么就是一个迭代器。同时迭代器还要有能力得到下一个数据,并且判断后面是否还有数据。

所以迭代与遍历的区别 迭代强调的是依次拿取数据,但是不强调要拿取的数据的数量 遍历强调的是拿取限定条件里面的所有数据

可能大家还是有点不太明白的,这里通过一段代码来演示,并且我们自己写一个简单的迭代器

const arr = ['a','b','c','d'];
//首先通过for循环来遍历数组集合,那么就会根据我们的要求,获取要求内的全部数据
for (let j = 0; j < 2; j++) console.log(brr[j]);

//我们来看迭代数据
//首先写一个基本的迭代器
var iterator = {
  i: 0,
  next() {
    return {
      value: brr[this.i++],
      done: this.i > brr.length,
    };
  },
};
console.log(iterator.next().value);//拿取到数据

在上面的代码中,第一个例子是通过遍历来拿取数据,这里会拿取到条件里所有的数据 。第二个是通过迭代器拿取数据,我们可以把对于获取数据的要求都写在迭代器中,让他去处理,而我们自己获取数据的时候,就不用考虑数组的长度等等。

这里我们来实践一下迭代器的使用场景,并且通过需求来写一个迭代器:依次获取n位的斐波拉契数列

//这里我们先不考虑使用函数然后递归来完成
//要依次拿到n位的数据,首先这里如果用数组来一次存储数据的话,因为n的不确定性,如果设置数组过长,浪费空间,过短的话,又得不到n的数据。
//因此迭代器就特别适合于这种,数据量特别大,或者无限长的数据

//设置来编辑一个迭代器,这里用函数来封装,通过迭代器,我们可以无限的获取到数据
function createFeiboIterator() {
  let pre1 = 1,
    pre2 = 1,
    n = 1;

  return {
    next() {
      let value = 0;

      if (n <= 2) value = 1;
      else value = pre1 + pre2;

      let result = {
        value,
        done: false,
      };
      pre1 = pre2;
      pre2 = result.value;
      n++;
      return result;
    },
  };
}
let a = createFeiboIterator();
console.log(a.next());
console.log(a.next());

可迭代协议

可迭代协议:再es6里面规定,如果一个对象具有知名符号Symbol.iterator,并且也是一个迭代器创建函数,那么这个对象就是一个可迭代对象。在es6后数组,类数组都已经是可迭代对象的了。

这是在控制台里面打印的一个普通数组 uTools_1688125710784.png

尽然数组是可迭代对象,并且有迭代函数,因此当然可以通过迭代器来打印了

     const arr = [1,2,3,4,5];
     let iterator = arr[Symbol.iterator]();
     let res = iterator.next();
     while(!res.done){
          console.log(res.value);
          res = iterator.next();
     }

for of

for of就是专门用来循环可迭代对象的,因此for of 也可以循环我们自己写的迭代对象

          let obj = {
            'a':'aa',
            'b':'bb',
            'c':'cc',
            'd':'dd',
            [Symbol.iterator] (){
                const keys = Object.keys(this);
                let i = 0;
                return {
                    next(){
                     let result= {
                        value: `${keys[i]}->${obj[keys[i]]}`,
                        done: i>=keys.length
                      }
                      i++;
                      return result;
                    }
                }
            }
          }
          for(const prop of obj) {
            console.log(prop);
          }

生成器

js中的生成器,是通过Generator构造函数,创建的对象。他最本质的作用就是方便我们来编写迭代器。

首先看一下下如何编写生成器
//这就是一个简单的生成器函数,调用他一定会的得到一个生成器的
function *createIterator(){}

const generator = createIterator();

这里我们在编写一段代码来对比下,自己编写迭代器与使用生成器的区别

//需要将obj对象的value进行迭代
let obj = {
  0: "a",
  1: "b",
  2: "c",
};

//1,我们自己编写一个迭代器来完成
let obj = {
  0: "a",
  1: "b",
  2: "c",
  [Symbol.iterator]() {
    const keys = Object.keys(this);
    let index = 0;
    return {
      next: () => {
        let result = {
          value: this[keys[index]],
          done: index >= keys.length,
        };
        index++;
        return result;
      },
    };
  },
};

//进行输出
let iterator = obj[Symbol.iterator]();
console.log(iterator.next());

//2,通过生成器的方式来实现
let obj = {
  0: "a",
  1: "b",
  2: "c",
};

function* createIterator(param) {
  const keys = Object.keys(param);
  for (const prop of keys) {
    yield param[prop];
  }
}

const generator = createIterator(obj);
console.log(generator.next());

是不是通过上面的代码,瞬间感觉到用生成器来来实现要简单很多的,这还是我们在处理简单的需求的基础上。

生成器的语法

  • 生成器内部的运行

     //在生成器里面,具有一个关键字yield,这个关键字的作用就是将后面的数据传递给我们每次通过next迭代的value值
     //同时我们每次调用next方法,都会运行生成器函数,运行到下一个yield关键字的位置。
     function *createIterator(){
         yield 1;
         yield 2;
         yield 3;
         
     } 
     const generator = createIterator();
     ```
    
  • 生成器传参

        //首先第一次next,传参无效,因为没有参数接收的。
        //后面的传参,都会是yield关键字的返回值,拿到参数后,可以进行使用
        function* createIterator() {
            let params = yield 1;
            yield 2 + params;
            yield 3;
        }
    
        const generator = createIterator();
        console.log(generator.next());
        console.log(generator.next("第二次运行"));