Skip to content

迭代器和生成器

迭代器

迭代

迭代就是按照顺序反复多次执行一段程序,通常会有明确的终止条件。

通过索引来迭代并不理想,有两个原因:

  1. 迭代之前需要事先知道如何使用数据结构,必须通过使用索引来迭代。
  2. 遍历赋值并不是数据结构固有的,并不是所有的数据类型都有索引值,所以有局限性。

因此产生了es6推出了迭代器和生成器。

迭代器模式

迭代器模式就是可迭代对象通过迭代器接口来迭代。

可迭代对象一般是数组和集合类型,还有计数循环等。

迭代器协议

迭代器是使用next( )方法在可迭代对象中遍历数据的,每次调用next( ),都返回一个IteratorResult对象,其中包含迭代器返回的下一个值,若不调用next( ),则无法知道迭代器当前的位置。返回的IteratorResult对象包含两个属性,done和value属性,done表示是否可再次调用next( ) ,value包含可迭代对象的下一个值。

实现迭代器协议(iterator protocol):在javascript中是实现迭代器协议即是实现了next() 方法的对象。

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

done(boolean)

如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)

如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为 迭 代结束之后的默认返回值。

value

迭代器返回的任何 JavaScript 值。done 为 true 时可省略

js
let arr = ['foo', 'baz'];
let iter = arr[Symbol.iterator]();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());

done为false时,value返回下一个值,

done为true时,value返回undefined

==注意:==

  1. 不同迭代器的实例对象之间没有联系,只会独立遍历可迭代对象

  2. 迭代时,数组元素改变,迭代器也会跟着改变,实时响应

js
//封装创建迭代器函数
function creatIterator(arr){
    let index=0;
    return {
        next:function () {
            if (index < arr.length) {
                return {done:false,value:arr[index++]}
            }else{
                return {done:true, value:undefined}
            }
        }
    }
}

可迭代对象

什么又是可迭代对象呢?

它和迭代器是不同的概念;

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

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

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

js
//封装可迭代对象
const info={
    names:['abc','cds','ddd'],
    [Symbol.iterator] : function () {
        let index=0;
        return {
            //箭头函数不绑定this,可以向上查找this,this指向info
            next: () => {
                if (index < this.names.length) {
                    return {done:false,value:this.names[index++]}
                }else{
                    return {done:true, value:undefined}
                }
            }
        }
    }
}

以下类型都实现了Iterable接口:

  1. 字符串
  2. 数组
  3. 映射
  4. 集合
  5. arguments对象
  6. NodeList等DOM集合类型

实现可迭代协议的所有类型都会自动兼容以下可迭代对象的任何语言特性。

接受可迭代对象的原生语言特性包括:

  1. for-of 循环
  2. 数组解构
  3. 扩展操作符
  4. Array.from( )
  5. 创建集合
  6. 创建映射
  7. Promise.all( )接受有期约组成的可迭代对象
  8. Promise.race( )接受由契约组成的可迭代对象
  9. yield操作符,在生成器中使用

自定义迭代类

在面向对象开发时,我们可以创建自己的类,那么当我们想让自己创建的类也能迭代,我们可以自己实现可迭代协议,使我们的对象可以迭代。

自定义教室类:教室中的学生可以迭代

js
//自定义可迭代类
class classRoom {
    constructor(name, address, students) {
        this.name = name;
        this.address = address;
        this.students = students;
    }

    //允许学生进入教室
    entry(stuName) {
        this.students.push(stuName);
    }

    //实现学生可迭代协议
    [Symbol.iterator]() {
        let index = 0;
        //返回一个迭代器
        return {
            //迭代器要实现迭代器协议,就是实现next函数
            next: () => {
                if (index < this.students.length) {
                    return {done: false, value: this.students[index++]};
                } else {
                    return {done: true, value: undefined};
                }
            },
            //迭代器提前终止,会调用return方法,我们可以在这里监听中断
            return() {
                console.log("迭代器提前结束了");
                return {done: true};
            },
        };
    }
}
const stu = new classRoom("1号", "c楼", ["one", "twp"]);
for (const s of stu) {
    console.log(s);
}

生成器

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

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

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

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

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

生成器事实上是一种特殊的迭代器;

MDN:

Instead, they return a special type of iterator, called a Generator.

yield

yield可以中断函数的执行,并且可以返回值

js
function* generator() {
    console.log('开始执行');

    let value1='100';
    console.log('第一段代码');
    yield value1

    let value2='200';
    console.log('第二段代码');
    yield value2

    let value3='300';
    console.log('第三段代码');
    yield value3

    let value4='400';
    console.log('第四段代码');
    yield value4
}
//生成器返回一个迭代器
const iter= generator();

console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
//执行结果
/*
开始执行
第一段代码
{ value: '100', done: false }
第二段代码
{ value: '200', done: false }
第三段代码
{ value: '300', done: false }
第四段代码
{ value: '400', done: false }
{ value: undefined, done: true }
{ value: undefined, done: true }
*/

next

next()方法可以让我们分块的调用函数,next还可以传递参数,让我们在相应的代码使用参数。

==注意:== 第一个next函数不传入参数,因为next传入参数相当于上一个yield有了返回值,我们可以获取参数,但是第一个next函数执行的第一段代码之前没有yield,所以第一个next函数不传参数。

第一段代码想传入参数,可以直接在调用生成器函数时直接传入传入参数。

js
function* generator(n) {
    console.log('开始执行');

    let value1=100;
    console.log('第一段代码');
    const m =yield value1 * n

    let value2=200;
    console.log('第二段代码');
    const q= yield value2 * m

    let value3=300;
    console.log('第三段代码');
    const y= yield value3 * q

    let value4=400;
    console.log('第四段代码');
    yield value4 * y

}
//生成器返回一个迭代器
const iter= generator(2);
console.log(iter.next());
console.log(iter.next(3));
console.log(iter.next(2));
console.log(iter.next(2));

return函数

使用return函数后,当前段和后面得代码都不会执行,如果return函数传入了参数,返回的value值就是传入的参数。

throw函数

throw方法会在暂停的时候将一个提供的错误注入到生成器对象中。如果错误未被处理,生成器就会关闭。

==注意:== 如果生成器对象还有开始执行,那么throw方法相当于在生成器函数外部抛出了异常,抛出的错误无法被捕获,直接导致生成器中断。

生成器替代迭代器

之前我们想让一个对象成为可迭代对象,我们会实现迭代器。但是我们有了生成器以后我们可以使用生成器来替代迭代器,因为生成器是一个特殊的迭代器。同样可以使用next()方法来调用。

js
//生成迭代器
function creatIterator(arr){
    let index=0;
    return {
        next:function () {
            if (index < arr.length) {
                return {done:false,value:arr[index++]}
            }else{
                return {done:true, value:undefined}
            }
        }
    }
}
//使用生成器替换迭代器实现可迭代对象
function* createGenerator(arr) {
  //1.通过循环yield来实现可迭代
    for (const item of arr) {
        yield item;
    }
  //2.yield*后面加上可迭代对象,生成一个可迭代对象,和for循环没有什么区别
  yield* arr;
}

异步代码处理方案

js
//发送请求
function requestData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(url);
    }, 1000);
  });
}

//1.多次回调,出现回调地狱
requestData("longzai").then((res) => {
  //再次发送请求
  requestData(res + "url1").then((res) => {
    console.log(res);
    requestData(res + "url2").then((res) => {
      console.log(res);
    });
  });
});

//2.通过Promise的then的返回Promise的特性来实现多次请求
//但是代码以不容易阅读
requestData("code")
  .then((res) => {
    console.log(res);
    return requestData(res + "url1");
  })
  .then((res) => {
    console.log(res);
  });

//3.通过Promise和Generator来实现数据请求
//
function* getData() {
  const res1 = yield requestData("coder");
  const res2 = yield requestData(res1 + "one");
  yield requestData(res2 + "two");
}
//创建生成器
const gen = getData();
// 第一个yield执行,然后返回一个Promise,
// gen.next()的value为一个Promise,然后执行then方法获取res
//手动调用
gen.next().value.then((res) => {
  console.log(res);
  gen.next(res).value.then((res) => {
    console.log(res);
    gen.next(res).value.then((res) => {
      console.log(res);
    });
  });
});

//创建自执行函数
function execFn(fn) {
  //生长器
  const gen = fn();
  function exec(url) {
    const result = gen.next(url);
    //如果生成器执行完了,直接return,结束执行
    if (result.done) {
      return;
    } else {
      //否则,获取结果后,继续发送请求
      result.value.then((res) => {
        exec(res);
        console.log(res);
      });
    }
  }
  exec();
}

execFn(getData);

//4. async和await方案
async function getData() {
  const res1 = await requestData("coder");
  const res2 = await requestData(res1 + "one");
  const res3 = await requestData(res2 + "two");
  console.log(res3);
}

getData();