第 81 期 - JavaScript 中迭代器与生成器的解析与应用
logoFRONTALK AI/1月13日 16:33/阅读原文

摘要

文章详细阐述了 JavaScript 中迭代器与生成器的概念、协议、应用场景、相互关系,还介绍了可迭代对象相关知识,最后展示了如何用生成器优化可迭代对象写法,并提及异步代码处理方案及迭代器助手提案。

1. 迭代器相关内容

1.1 迭代器的概念

迭代器(iterator)用于在容器对象(如数组、链表等)上进行遍历。在 JavaScript 中,迭代器是一个遵循迭代器协议的对象,其关键在于具有特定的next方法。这个next方法在调用时会返回一个包含value(当前产生的值)和done(表示是否所有值都已迭代完毕的布尔值)的对象。例如创建一个基础的迭代器:

const iterator = {
  next: function() {
    return { done: true, value: 'coderwhy' }
  }
};

1.2 可迭代对象与迭代器协议

可迭代对象和迭代器是不同的概念,迭代协议分为可迭代协议和迭代器协议。

1.2.1 可迭代协议

一个对象要被for...of循环使用,必须实现可迭代协议,即具有[Symbol.iterator]方法。可迭代对象可以看作是一种抽象的数据集合,可以进行迭代。例如:

const iterableObj = {
  names: ["coderwhy", "XiaoYu", "JS"],
  [Symbol.iterator]: function() {
    let index = 0;
    return {
      next: () => {
        if (index < this.names.length) {
          return { done: false, value: this.names[index ++] };
        } else {
          return { done: true, value: undefined };
        }
      }
    };
  }
};

1.2.2 迭代器协议

迭代器对象必须实现next方法,返回{ value, done }。迭代器对象更倾向于描述一个“过程”,是逐次产出元素的工具。

1.3 原生迭代器对象

JavaScript 中部分原生对象如 String、Array、TypeArray、Map、Set 以及 Intl.Segments 已经实现了可迭代协议,会生成一个迭代器对象。这就是为什么数组等数据结构可以直接进行迭代操作。

1.4 自定义类对象可迭代性

在面向对象开发中,若希望自定义类创建的对象可迭代,需要在类中添加[Symbol.iterator]方法。例如定义Classroom类时添加该方法,就可以使Classroom类创建的对象可迭代。

2. 生成器相关内容

2.1 生成器的基本概念

生成器(Generator)是 ES6 引入的一种特殊函数类型,通过在function后加*定义。在生成器函数内部,可以使用yield关键字暂停函数执行并返回一个值。生成器函数调用时会返回一个生成器对象,该对象可调用next方法来控制函数的执行与暂停。例如:

function* foo() {
  console.log("函数开始执行~");
  const value1 = 100;
  console.log("第一段代码:", value1);
  yield;
  const value2 = 200;
  console.log("第二段代码:", value2);
  yield;
  const value3 = 300;
  console.log("第三段代码:", value3);
  yield;
  console.log("函数执行结束~");
  return "123";
}
const generator = foo();
generator.next();
generator.next();
generator.next();
generator.next();

2.2 生成器函数执行流程

生成器函数返回的生成器对象是一个特殊的迭代器,其next方法遵守迭代器对象的返回值标准。yield关键字有多种用法,如[rv] = yield [expression],可以使生成器函数在每个yield处暂停执行,并且yield具备双向通信功能。

2.3 yield 错位双向传递

生成器函数和调用者是交替执行的,这种机制导致了yield的输出(返回值给调用者)和next(value)的输入(传入值给生成器)发生在两个不同的阶段,即错位传递。这是由生成器的暂停和恢复机制决定的,如果没有这种错位传递,在单线程的 JavaScript 中会出现多种问题。

2.4 生成器的其他方法使用

2.4.1 生成器的终止执行

Generator.prototype.return方法用于提前终止生成器函数的执行,同时可以指定一个返回值。调用return(value)后,生成器会立即停止执行,后续的next()调用不会再执行生成器函数内部的代码。

2.4.2 生成器的抛出异常

Generator.prototype.throw()方法用于向生成器函数内部抛出一个错误,在生成器暂停的yield表达式处抛出异常。根据生成器内部是否捕获异常,会有不同的处理结果。

3. 可迭代对象写法的优化

3.1 一般优化

通过学习生成器之后,可以简化可迭代对象的写法。例如使用生成器函数替代[Symbol.iterator]方法,还可以使用yield*语法糖进一步精简代码。

3.2 迭代数字案例

在迭代数字的案例中,使用生成器的代码相比迭代器更加简洁,这体现了生成器是迭代器的更高抽象层次封装。

3.3 自定义类对象优化

对于自定义类对象的可迭代性,也可以通过生成器进行完善,如在Classroom类的[Symbol.iterator]方法中使用yield*来简化代码。

4. 异步代码的处理方案

4.1 方式 1:层层嵌套

使用 Promise 的thencatch方法处理异步数据获取时,如果多次处理数据,很容易形成回调地狱,例如多层嵌套的网络请求。

4.2 方式 2:链式调用

相对于回调地狱,链式调用是一种更好的方式,then方法可以接收上一阶段返回的内容进行调用。

4.3 方案 3:Generator 方案

结合生成器函数,通过yield可以与服务器进行交互,实现同步代码的效果。但手动执行生成器函数时会面临嵌套问题,因此可以封装一个工具函数execGenerator来自动执行生成器函数。此外,还有co这个 npm 库也实现了类似功能,通过生成器函数实现协程,简化异步代码的编写。

5. 迭代器助手提案

「 Stage 4 」迭代器助手提案引入了一系列新的迭代器原型方法,如mapfilter等。该提案将使迭代器在处理大型或无限可枚举数据集时更加方便,有望在 ES16 纳入规范并发布。

 

扩展阅读

Made by 捣鼓键盘的小麦 / © 2025 Front Talk 版权所有