JavaScript迭代器和生成器

JavaScript 提供了许多迭代集合的方法,从简单的 for 循环到 forEach、 map 和 filter。其中 while、do...while、for、for...in、for...of,可以被 label 标识,可以被 break 或者 continue 来指出程序是否该停止循环还是继续循环。

while、do...while、for 能够循环数组,如果要迭代对象则需要结合 Object.getOwnPropertyNames(不包括 symbol 类型)。for...in 能够循环对象(包括数组),但是它会循环对象的所有可枚举属性,包括原型链上的属性。因此,通常需要结合 hasOwnProperty 对象实例方法来过滤。

ECMAScript 2015 引入 Object.keys 返回对象 key 的数组。ECMAScript 2017 引入 Object.values 返回对象 value 的数组,Object.entries 返回对象的键值对数组。它们都是是对象自身的(不含继承的)所有可遍历(enumerable)属性的键或值,但不包括 Symbol 属性。

与之配套的是 Object.getOwnPropertySymbols,返回包含对象自身的所有 Symbol 属性键名的数组。还可以使用 Reflect.ownKeys,返回包含对象自身的(不含继承的)所有键名的数组,不管键名是 Symbol 或字符串,也不管是否可枚举。

判断对象的属性是自身的属性和继承的属性,除了 hasOwnProperty 对象实例方法,ECMAScript 2022 引入 Object.hasOwn,判断是否为自身的属性。Object.hasOwn 的一个好处是,对于不继承 Object.prototype 的对象不会报错,而 hasOwnProperty 是会报错的。

除了数组和对象,ECMAScript 2015+ 添加了新的数据结构 Map、Set。为了处理所有不同数据结构,就需要一种统一的接口机制。遍历器(Iterator)就是这样一种机制,它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。

Iterator 的作用有三个:

  • 为各种数据结构,提供一个统一的、简便的访问接口。
  • 使得数据结构的成员能够按某种次序排列。
  • 创造了一种新的遍历命令 for...of 循环,Iterator 接口供 for...of 消费。

迭代器

在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。具体来说,迭代器是通过具有 next() 方法来实现 Iterator 协议的任何对象,该方法返回具有两个属性的对象:

  • value:迭代序列中的 next 值。
  • done:如果已经迭代到序列中的最后一个值,则为 true 。如果 value 与 done 一起出现,它则是迭代器的返回值。

创建迭代器对象后,可以通过重复调用 next() 来显式迭代。对迭代器进行迭代被称为消耗迭代器,因为它通常只能执行一次。产生终止值后,对 next() 的其他调用应继续返回 {done: true} 。

生成器函数

虽然自定义迭代器是一个有用的工具,但由于需要显式维护其内部状态,因此它们的创建需要仔细编程。生成器函数提供了一个强大的替代方案:它们允许您通过编写执行不连续的单个函数来定义迭代算法。生成器函数使用 function* 语法编写。

调用时,生成器函数最初并不执行其代码。相反,它们返回一种特殊类型的迭代器,称为生成器。当通过调用生成器的 next 方法使用某个值时,生成器函数将执行直到遇到 yield 关键字。该函数可以根据需要多次调用,并且每次都会返回一个新的生成器。每个生成器只能迭代一次。

迭代协议

迭代协议不是新的内置函数或语法,而是协议。这些协议可以由任何对象通过遵循一些约定来实现。迭代协议具体分为两个协议有两种协议:可迭代协议和迭代器协议。

可迭代协议

可迭代协议(iterable protocol)允许 JavaScript 对象定义或自定义其迭代行为,例如在 for...of 构造中循环哪些值。某些内置类型是具有默认迭代行为的内置可迭代对象,例如 Array 或 Map ,而其他类型则不是。

为了可迭代,对象必须实现 @@iterator 方法,这意味着该对象(或者它原型链上的某个对象)必须具有带有 @@iterator 键的属性,该属性可通过常量 Symbol.iterator 获得:

  • [Symbol.iterator]:一个无参数的函数,其返回值为一个符合迭代器协议的对象。

每当需要迭代对象时,都会不带参数调用其 @@iterator 方法,并使用此方法返回的迭代器获得要迭代的值。值得注意的是调用此无参数函数时,它将作为对可迭代对象的方法进行调用。因此,在函数内部, this 关键字可用于访问可迭代对象的属性,以决定在迭代期间提供什么。

该函数可以是普通函数,也可以是生成器函数,以便在调用时返回迭代器对象。在此生成器函数内部,可以使用 yield 提供每个条目。

迭代器协议

迭代器协议(iterator protocol)定义了一种生成值序列(有限或无限)的标准方法,并且在生成所有值时可能会返回一个返回值。当对象实现具有以下语义的 next() 方法时,该对象就是迭代器:

  • next():无参数或者接受一个参数的函数,并返回符合 IteratorResult 接口的对象。如果在使用迭代器内置的语言特征,得到一个非对象返回值,将会抛出 TypeError("iterator.next() returned a non-object value")。

所有迭代器协议方法(next()、return() 和 throw())都应返回实现 IteratorResult 接口的对象。它必须具有以下属性:

  • done:如果迭代器能够生成序列中的下一个值,则为 false 的布尔值。如果迭代器已完成其序列,则具有值 true 。在这种情况下, value 可以选择指定迭代器的返回值。
  • value:迭代器返回的任何 JavaScript 值。当 done 为 true 时可以省略。

实际上,这两种属性都不是严格要求的。如果返回没有任一属性的对象,则它实际上相当于 { done: false, value: undefined }。如果迭代器返回 done: true 的结果,则对 next() 的任何后续调用也预计会返回 done: true,尽管这在语言级别上不强制执行。

next 方法可以接受一个值,该值将提供给方法体。任何内置的语言特征都将不会传递任何值。传递给生成器 next 方法的值将成为相应 yield 表达式的值。或者,迭代器还可以实现 return(value) 和 throw(exception) 方法,这些方法在被调用时告诉迭代器调用者已完成迭代,并且可以执行任何必要的清理。

  • return(value):无参数或者接受一个参数的函数并返回符合 IteratorResult 接口的对象,通常 value 等于传入的 value 和 done 。调用此方法告诉迭代器调用者不打算进行任何更多的 next() 调用,并且可以执行任何清理操作。
  • throw(exception):无参数或者接受一个参数的函数,并返回符合 IteratorResult 接口的对象,通常 done 等于 true。调用此方法告诉迭代器调用者检测到错误条件,并且 exception 通常是 Error 实例。

异步迭代器和异步可迭代协议

还有另一对用于异步迭代的协议,称为异步迭代器(async iterator)和异步可迭代协议(async iterable protocols)。与可迭代和迭代器协议相比,它们具有非常相似的接口,只是调用迭代器方法的每个返回值都包装在一个 Promise 中。当对象实现以下方法时,它就实现了异步可迭代协议:

  • [Symbol.asyncIterator]:返回对象的无参数函数,并且符合异步迭代器协议。

当对象实现以下方法时,它就实现了异步迭代器协议:

  • next():无参数或者接受一个参数的函数,并返回 promise。 Promise 会满足一个符合 IteratorResult 接口的对象,并且属性与同步迭代器的属性具有相同的语义。
  • return(value):无参数或者接受一个参数的函数,并返回 promise。 Promise 会满足一个符合 IteratorResult 接口的对象,并且属性与同步迭代器的属性具有相同的语义。
  • throw(exception):无参数或者接受一个参数的函数,并返回 promise``。Promise 会满足一个符合 IteratorResult 接口的对象,并且属性与同步迭代器的属性具有相同的语义。

语言和迭代协议之间的交互

String、Array、TypedArray、Map、Set 以及 Intl.Segments 都是内置的可迭代对象,因为它们的每个 prototype 对象都实现了 @@iterator 方法。此外,arguments 对象和一些 DOM 集合类型,如 NodeList 也是可迭代的。ReadableStream 是唯一的内置异步可迭代对象。

生成器函数返回生成器对象,它们是可迭代的迭代器。异步生成器函数返回异步生成器对象,它们是异步可迭代迭代器。

从内置迭代器返回的迭代器实际上都继承自一个公共类 Iterator,该类实现了 [Symbol.iterator]() { return this; } 方法,使它们都是可迭代迭代器。

有许多 API 接受可迭代对象,包括:Map()、WeakMap()、Set()、WeakSet()、Promise.all()、Promise.allSettled()、Promise.race()、Promise.any()、Array.from()、Object.groupBy()、Map.groupBy()。

相关推荐
货拉拉技术3 小时前
一句话叫车:货拉拉小程序 AI 叫车实战
前端·agent·ai编程
yunyi4 小时前
给桌面客户端做下载加速:从「桶直接公开」到「边缘优先、源站兜底」的三代演进
前端·后端
world78112264 小时前
【C++】set 和 map
java·前端·c++·set·map
程序猿乐锅4 小时前
Streamlit 实现 AI 聊天 Agent:角色提示词、流式输出与会话管理
开发语言·前端·笔记·后端·python·学习
niucloud-admin4 小时前
NIUCLOUD PHP SAAS上门家政使用文档:客户报表
前端·人工智能·ai编程
漫步人生走在路上4 小时前
外贸B2B行业GEO优化白皮书:从0到1完整落地流程
前端·人工智能·搜索引擎·chatgpt·twitter
程序猿乐锅4 小时前
面向对象练习:图书借阅系统里的封装、抽象类与多态
开发语言·前端·数据库·后端·python·学习·1024程序员节
老木避暑研究所4 小时前
负氧离子数据的采集、清洗与可视化:一套可复用的环境监测数据分析模板
前端·人工智能·数据分析
计算机魔术师5 小时前
AI 推翻了一个当了30年地基的假设,查的人恰好是立论者
前端
范同学~5 小时前
项目添加水印
java·前端·javascript