JS笔记迭代器与生成器

本文深入探讨了JavaScript中的迭代器和生成器。迭代器协议使得各种数据结构可以被遍历,而生成器则提供了暂停和恢复执行的能力,简化了复杂的迭代逻辑。通过实例展示了如何自定义迭代器、使用生成器进行输入输出以及如何提前终止迭代。这些特性在处理大量数据、实现复杂算法和优化内存使用等方面具有重要意义。
摘要由CSDN通过智能技术生成

五、迭代器与生成器

5.1 迭代器
1 理解迭代
  • 循环是迭代机制的基础,因为它可以指定迭代的次数,以及每次迭代要执行什么操作
    • 数组可以通过递增索引来遍历,但是由于迭代之前需要实现知道如何使用数据结构(数组中的每一项需要先引用取得数组对象,然后通过【】操作符取得索引位置上的项,所以不是所有的数据结构都适用),以及遍历的顺序不是数据结构所固有的(索引遍历不适合于具有隐式顺序的数据结构)
  • ES5新增了Array.prototype.forEach()方法
    • 这个方法解决了单独记录索引和通过数组对象取得值的问题,不过没有办法标识迭代何时终止,因此这个方法值适用于数组,而且回调结构也比较笨拙
  • 在早期的ES较早的版本,执行迭代必须使用循环或其他辅助结构,随着代码量增加,代码会变得越发混乱。ES6提出了迭代器模式。
2 迭代器模式
  • 迭代器模式把有些结构称为‘可迭代对象’(数组、集合这样的,包含的元素是有限的,而且都具有无歧义的遍历顺序)
  • 可迭代对象不一定是集合对象,也可以是仅仅具有类似数组行为的其他数据结构。
3 可迭代协议
  • 实现Iterable接口所需的两种能力:支持迭代器的自我识别能力和创建实现Iterable接口的对象的能力

  • 实现了Iterable接口的内置对象

    1. 字符串

    2. 数组

    3. 映射

    4. 集合

    5. arguments对象

    6. NodeList等DOM集合类型

      // 检查是否存在默认迭代器属性
      // 没有实现迭代器工厂函数
      let num = 1;
      let obj = {};
      console.log(num[Symbol.iterator]); // undefined
      console.log(obj[Symbol.iterator]); // undefined
      
      // 实现了迭代器工厂函数
      let str = 'abc';
      let arr = ['a','b','c'];
      let map = new Map().set('a',1).set('b',2).set('c',3);
      let set = new Set().add('a').add('b').add('c');
      
      console.log(str[Symbol.iterator]); // [Function: [Symbol.iterator]]
      console.log(arr[Symbol.iterator]); // [Function: values]
      console.log(map[Symbol.iterator]); // [Function: entries]
      console.log(set[Symbol.iterator]); // [Function: values]
      
      console.log(str[Symbol.iterator]()); // Object [String Iterator] {}
      console.log(arr[Symbol.iterator]()); // Object [Array Iterator] {}
      console.log(map[Symbol.iterator]()); // [Map Entries] { [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] }
      console.log(set[Symbol.iterator]()); // [Set Iterator] { 'a', 'b', 'c' }
      
      
      
    
  • 接收可迭代对象的原生语言特性包括

    1. for-of循环
    2. 数组解构
    3. 扩展操作符
    4. Array.from()
    5. 创建集合
    6. 创建映射
    7. Promise.all()接收由期约组成的可迭代对象
    8. Promise.race()接收由期约组成的可迭代对象
    9. yield*操作符
    let arr = ['foo','bar','baz'];
    
    // for-of循环
    console.log('for-of:');
    for (let el of arr) {
      console.log(el);
    }
    // 数组解构
    console.log('数组解构:');
    let [a,b,c] = arr;
    console.log(a,b,c);
    // Set构造函数
    console.log('Set构造函数:');
    let set = new Set(arr);
    console.log(set);
    // Map构造函数
    console.log('Map构造函数:');
    let pairs = arr.map((x,i) => [x,i]);
    console.log(pairs);
    let map = new Map(pairs);
    console.log(map);
    // Array.from()
    console.log('Array.from():');
    let arr2 = Array.from(arr);
    console.log(arr2);
    // 扩展操作符
    console.log('扩展操作符:');
    let arr3 = [...arr];
    console.log(arr3);
    
    /*
    for-of:
    foo
    bar
    baz
    数组解构:
    foo bar baz
    Set构造函数:
    Set(3) { 'foo', 'bar', 'baz' }
    Map构造函数:
    [ [ 'foo', 0 ], [ 'bar', 1 ], [ 'baz', 2 ] ]
    Map(3) { 'foo' => 0, 'bar' => 1, 'baz' => 2 }
    Array.from():
    [ 'foo', 'bar', 'baz' ]
    扩展操作符:
    [ 'foo', 'bar', 'baz' ]
    */
    
4 迭代器协议
  • 迭代器是一种一次性使用的对象,用于迭代与其关联的可迭代对象。迭代器API使用next()方法在可迭代对象中遍历数据,每次成功的调用next(),都会返回一个IteratorResult对象,其中包含迭代器的下一个值,如果不调用next(),则无法知道迭代器的当前位置。
  • IteratorResult包含两个属性:done、value。
    • done:布尔值,表示是否可以再次调用next()取得下一个值(false,表示后面还有值。true:状态表示‘耗尽’)
    • value:包含可迭代对象的下一个值
let arr = ['foo','bar'];
console.log(arr[Symbol.iterator]);
let iter = arr[Symbol.iterator]();
console.log(iter);
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());

/*
[Function: values]
Object [Array Iterator] {}
{ value: 'foo', done: false }
{ value: 'bar', done: false }
{ value: undefined, done: true }
*/

  • 每个迭代器都表示对可迭代对象的一次性有序遍历,不同迭代器的实例相互之间没有联系。
  • 迭代器维护着一个指向可迭代对象的引用,因此迭代器会阻止垃圾回收程序回收可迭代对象
5 自定义迭代器
  • class Counter {
      constructor(limit) {
        this.count = 1;
        this.limit = limit;
      }
    
      next() {
        if(this.count <= this.limit) {
          return {done: false, value: this.count++};
        }else {
          return {done: true, value: undefined};
        }
      }
      [Symbol.iterator]() {
        return this;
      }
    }
    
    let counter = new Counter(4);
    for(let el of counter) {
      console.log(el); // 1 2 3 4
    }
    

    ​ 该类实现了一个Iterator接口,但是里面的每个对象只能被迭代一次。在上面代码的基础上再次输出,结果没有任何东西。因此,如果把计数器变量放到闭包里面,然后通过闭包返回迭代器,就能解决这个问题。

    for(let el of counter) {
      console.log(el);
    }
    
  • class Counter {
      constructor(limit) {   
        this.limit = limit;
      }
    
      [Symbol.iterator]() {
        let count = 1;
        let limit = this.limit;
        return {
          next() {
            if(count <= limit) {
              return {done: false, value: count++};
            }else {
              return {done: true, value: undefined};
            }
          }
        }
      }
    }
    
    let counter = new Counter(4);
    for(let el of counter) {
      console.log(el);
    }
    for(let el of counter) {
      console.log(el); 
    }
    /*
    1 2 3 4 1 2 3 4
    */
    
6 提前终止迭代器
  • return()方法用于指定在迭代器提前关闭时执行的逻辑

    class Counter {
      constructor(limit) {   
        this.limit = limit;
      }
    
      [Symbol.iterator]() {
        let count = 1;
        let limit = this.limit;
        return {
          next() {
            if(count <= limit) {
              return {done: false, value: count++};
            }else {
              return {done: true, value: undefined};
            }
          },
          return() {
            console.log('Exiting early');
            return {done:true}
          }
        }
      }
    }
    let counter = new Counter(5);
    for(let i of counter) {
      // 1.提前结束
      if(i > 2) {
        break;
      }
      console.log(i);
        /*
    1	
    2
    Exiting early
        */
    }
    
    // 2. try/catch捕捉
    try {
      for(let i of counter) {
        if(i > 2) {
          throw 'err'
        }
        console.log(i);
      }
    } catch (error) {console.log();}
    /*
    1
    2
    Exiting early
    */
    
    // 3.解构操作并未消耗所有值
    let [a,b] = counter;
    console.log(a,b);
    /*
    Exiting early
    1 2
    */
    
    // 4.如果迭代器关闭,则可以继续从上次离开的地方继续迭代。数组的迭代器就是不能关闭的
    let a = [1,2,3,4,5];
    let iter = a[Symbol.iterator]();
    for(let i of iter) {
      if(i > 2) {
        break
      }
      console.log(i);
    }
    console.log("第一次结束,第二次开始");
    for(let i of iter) {
      console.log(i);
    }
    /*
    1
    2
    第一次结束,第二次开始
    4
    5
    */
    
5.2 生成器

​ ES6新增的一个极其灵活的结构,拥有在一个函数块内暂停和恢复代码执行的能力

1 生成器基础
  • 定义方法
// 1.生成器函数声明
function* generatorFn() {}
// 2.生成器的函数表达式
let generatorFn = function* () {}
// 3.作为对象字面量方法的生成器函数
let Foo = {
    * generatorFn() {}
}
// 4.作为静态方法的生成器函数
class Bar {
    static * generatorFn() {}
}
// 箭头函数不能用来定义生成器函数
  • 调用生成器会产生一个生成器对象,一开始处于暂停执行的状态,生成器对象也执行了Iterator接口,因此也含有next()方法,调用这个方法会让生成器开始或恢复执行。
function* gen() {}
const g = gen();
console.log(g);
console.log(g.next);
console.log(g.next());
/*
Object [Generator] {}
[Function: next]
{ value: undefined, done: true }
*/
  • 生成器函数只会在初次调用next()方法后开始执行,生成器对象实现了Iterator接口,默认迭代器是自引用的。
2 通过yield中断执行
  • yield可以让生成器停止和开始执行,停止时,函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用next()方法来恢复执行

    function* gen() {
      yield;
    }
    
    let g = gen();
    console.log(g.next());
    console.log(g.next());
    
    /*
    { value: undefined, done: false }
    { value: undefined, done: true }
    */
    
  • yield生成的值会出现在next()方法返回的对象里,通过yield退出的生成器函数会处在done:false状态,通过return退出会处于done:true状态

    function* gen() {
      yield 'foo';
      yield 'bar';
      return 'baz';
    }
    
    let g = gen();
    console.log(g.next());
    console.log(g.next());
    console.log(g.next());
    
    /*
    { value: 'foo', done: false }
    { value: 'bar', done: false }
    { value: 'baz', done: true }
    */
    
  • 生成器的每个对象互不影响,yield关键字只能在生成器函数内部使用,在外部或者出现到嵌套非生成器的函数中会抛出语法错误。

  • 生成器作为可迭代对象来使用非常方便

    function* gen() {
      yield 1;
      yield 2;
      yield 3;
    }
    for(const x of gen()) {
      console.log(x);
    }
    /*
    1
    2
    3
    */
    
    // 当我们需要定义一个可迭代对象,而他会产生一个迭代器,这个迭代器会执行指定的次数。
    function* gen(n) {
      while(n--) {
        yield;
      }
    }
    for(let _ of gen(4)) {
      console.log('foo');
    }
    /*
    foo
    foo
    foo
    foo
    */
    
  • 使用yield实现输入和输出

    // 上一次让生成器函数暂停的yield关键字会接收到传给next()方法的第一个值,第一次调用next()传入的值不会被使用,因为这一次调用是为了开始执行生成器函数。
    function* gen(v) {
      console.log(v);
      console.log(yield);
      console.log(yield);
    }
    
    let gen1 = gen('foo');
    gen1.next('bar'); // foo 这个传入的参数不会被使用
    gen1.next('baz'); // baz
    gen1.next('qux'); // qux
    
  • 通过‘*’增强yield的行为,让它产生一个可迭代对象,从而一次产生一个值

    function* gen() {
      yield* [1,2,3];
      //  等价于
      // for(const x of [1,2,3]) {
      //   yield x;
      // }
    }
    
    for(const x of gen()) {
      console.log(x);
    }
    

    而yield*的值是关联迭代器返回done:true时value属性

    function* inner() {
      yield 'foo';
      return 'bar';
    }
    
    function* outer() {
      console.log('iter value:',yield* inner());
    }
    
    for(const x of outer()) {
      console.log('value',x);
    }
    /*
    value foo
    iter value: bar
    */
    
  • yield实现递归算法(待解决

3 生成器作为默认迭代器
class Foo {
  constructor() {
    this.values = [1,2,3];
  }
  *[Symbol.iterator]() {
    yield* this.values;
  }
}
const f = new Foo();
for(const x of f) {
  console.log(x); // 1 2 3
}
4 提前终止生成器

​ 生成器除了和迭代器一样拥有next()、return(),还有一个throw()方法。

  • return()方法会强制生成器进入关闭状态,提供给return的值就是终止迭代对象的值。

    function* gen() {
      for (const x of [1,2,3]) {
        yield x;
      }
    }
    const g =  gen();
    
    console.log(g); // gen{<suspended>}
    console.log(g.return(4)); // {done:true,value:4}
    console.log(g); // gen{<closed>}
    
    
  • 与迭代器不同的是,所有的生成器对象都有return方法,只要通过它进入关闭状态,就无法恢复,后续通过调用next()会显示done:true状态,而提供的任何返回值都不会被存储或传播

    function* gen() {
      for (const x of [1,2,3]) {
        yield x;
      }
    }
    const g =  gen();
    
    console.log(g.next());
    console.log(g.return(4));
    console.log(g.next());
    console.log(g.next());
    /*
    { value: 1, done: false }
    { value: 4, done: true }
    { value: undefined, done: true }
    { value: undefined, done: true }
    */
    

    for-of会忽略done:true的值

    function* gen() {
      for (const x of [1,2,3]) {
        yield x;
      }
    }
    const g =  gen();
    for (const x of g) {
      if(x > 1) {
        g.return(4)
      }
      console.log(x); // 1 2
    }
    
  • throw()方法会在暂停时将一个提供的错误注入到生成器对象中,如果错误未被处理,生成器就会关闭

    function* gen() {
      for (const x of [1,2,3]) {
        yield x;
      }
    }
    const g =  gen();
    console.log(g); // gen{<suspended>}
    try {
      g.throw('foo');
    } catch(e) {
      console.log(e); // foo
    }
    console.log(g); // gen{<closed>}
    

    如果在生成器内部处理了这个错误,那么生成器就不会关闭,而且还可以恢复执行,错误处理会跳过相应的yield

    function* gen() {
      for (const x of [1,2,3]) {
        try {
          yield x;
        } catch (error) {}
      }
    }
    const g = gen();
    console.log(g.next());
    g.throw('foo');
    console.log(g.next());
    
    /*
    { value: 1, done: false }
    { value: 3, done: false }
    */
    
5.3 总结
  1. 迭代器是一个由任意对象实现的接口,支持连续获得对象产出的每一个值,而生成器是一种特殊的函数,调用之后会返回一个生成器对象,它们都实现了Iterator接口。
  2. 迭代器必须通过连续调用next()方法才能连续取得值,这个方法返回一个IteratorObject对象,包含一个done属性和一个value属性,前者是一个布尔值,表示是否还有更多的值可以访问,后者包含迭代器当前的值。
  3. 生成器支持yield关键字,能够暂停执行生成器函数,使用yield关键字可以通过next()方法接收输入和产生输出,在加上‘*’后,还可以将跟在它后面的可迭代对象序列化为一连串的值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

madkeyboard

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值